001package com.box.sdk; 002 003import java.net.MalformedURLException; 004import java.net.URL; 005import java.util.Iterator; 006import java.util.NoSuchElementException; 007 008import com.eclipsesource.json.JsonArray; 009import com.eclipsesource.json.JsonObject; 010import com.eclipsesource.json.JsonValue; 011 012/** 013 * Common implementation for paging support. 014 * 015 * @param <T> 016 * type of iterated entity 017 */ 018public abstract class BoxResourceIterable<T> implements Iterable<T> { 019 020 /** 021 * Parameter for max page size. 022 */ 023 public static final String PARAMETER_LIMIT = "limit"; 024 025 /** 026 * Parameter for marker for the beginning of next page. 027 */ 028 public static final String PARAMETER_MARKER = "marker"; 029 030 /** 031 * Body Parameter for marker for the beginning of next page. 032 */ 033 public static final String BODY_PARAMETER_MARKER_NEXT = "next_marker"; 034 035 /** 036 * Body parameter for page entries. 037 */ 038 public static final String BODY_PARAMETER_ENTRIES = "entries"; 039 040 /** 041 * The API connection to be used by the resource. 042 */ 043 private final BoxAPIConnection api; 044 045 /** 046 * To end-point with paging support. 047 */ 048 private final URL url; 049 050 /** 051 * The maximum number of items to return in a page. 052 */ 053 private final int limit; 054 055 /** 056 * The iterator that gets the next items. 057 */ 058 private final IteratorImpl iterator; 059 060 /** 061 * Constructor. 062 * 063 * @param api the API connection to be used by the resource 064 * @param url endpoint with paging support 065 * @param limit the maximum number of items to return in a page 066 */ 067 public BoxResourceIterable(BoxAPIConnection api, URL url, int limit) { 068 this(api, url, limit, null, null); 069 } 070 071 /** 072 * Constructor. 073 * 074 * @param api the API connection to be used by the resource. 075 * @param url to endpoint with paging support. 076 * @param limit the maximum number of items to return in a page. 077 * @param body the body to send to the requested endpoint. 078 */ 079 public BoxResourceIterable(BoxAPIConnection api, URL url, int limit, JsonObject body) { 080 this(api, url, limit, body, null); 081 } 082 083 /** 084 * Constructor. 085 * 086 * @param api the API connection to be used by the resource. 087 * @param url to endpoint with paging support. 088 * @param limit the maximum number of items to return in a page. 089 * @param marker the marker where the iterator will begin 090 */ 091 public BoxResourceIterable(BoxAPIConnection api, URL url, int limit, String marker) { 092 this(api, url, limit, null, marker); 093 } 094 095 /** 096 * Constructor. 097 * 098 * @param api the API connection to be used by the resource. 099 * @param url to endpoint with paging support. 100 * @param limit the maximum number of items to return in a page. 101 * @param body the body to send to the requested endpoint. 102 * @param marker the marker where the iterator will begin 103 */ 104 public BoxResourceIterable(BoxAPIConnection api, URL url, int limit, JsonObject body, String marker) { 105 this.api = api; 106 this.url = url; 107 this.limit = limit; 108 this.iterator = new IteratorImpl(marker, body); 109 } 110 111 /** 112 * Factory to build a new instance for a received JSON item. 113 * 114 * @param jsonObject 115 * of the item 116 * @return the item instance 117 */ 118 protected abstract T factory(JsonObject jsonObject); 119 120 /** 121 * Builds internal read-only iterator over {@link BoxResource}-s. 122 * 123 * @return iterator implementation 124 * @see Iterable#iterator() 125 */ 126 @Override 127 public Iterator<T> iterator() { 128 return this.iterator; 129 } 130 131 /** 132 * Builds internal read-only iterator over {@link BoxResource}-s. 133 * 134 * @return iterator implementation 135 * @see Iterable#iterator() 136 */ 137 public String getNextMarker() { 138 return this.iterator.markerNext; 139 } 140 141 /** 142 * Paging implementation. 143 */ 144 private class IteratorImpl implements Iterator<T> { 145 146 /** 147 * Base 64 encoded string that represents where the paging should being. It should be left blank to begin 148 * paging. 149 */ 150 private String markerNext; 151 152 /** 153 * Current loaded page. 154 */ 155 private JsonArray page; 156 157 /** 158 * Cursor within the page (index of a next item for read). 159 */ 160 private int pageCursor; 161 162 /** 163 * The body to include in the request. 164 */ 165 private JsonObject body; 166 167 /** 168 * Constructor. 169 * 170 * @param marker 171 * the marker at which the iterator will begin 172 */ 173 IteratorImpl(String marker, JsonObject body) { 174 this.markerNext = marker; 175 this.body = body; 176 this.loadNextPage(); 177 } 178 179 /** 180 * Loads next page. 181 */ 182 private void loadNextPage() { 183 String existingQuery = BoxResourceIterable.this.url.getQuery(); 184 QueryStringBuilder builder = new QueryStringBuilder(existingQuery); 185 builder.appendParam(PARAMETER_LIMIT, BoxResourceIterable.this.limit); 186 if (this.markerNext != null) { 187 if (this.body != null) { 188 this.body.set("marker", this.markerNext); 189 } else { 190 builder.appendParam(PARAMETER_MARKER, this.markerNext); 191 } 192 } 193 194 URL url; 195 try { 196 url = builder.addToURL(BoxResourceIterable.this.url); 197 } catch (MalformedURLException e) { 198 throw new BoxAPIException("Couldn't append a query string to the provided URL."); 199 } 200 201 BoxAPIRequest request = null; 202 if (this.body != null) { 203 request = new BoxAPIRequest(BoxResourceIterable.this.api, url, "POST"); 204 request.setBody(this.body.toString()); 205 request.addHeader("Content-Type", "application/json"); 206 } else { 207 request = new BoxAPIRequest(BoxResourceIterable.this.api, url, "GET"); 208 } 209 210 BoxJSONResponse response = (BoxJSONResponse) request.send(); 211 JsonObject pageBody = JsonObject.readFrom(response.getJSON()); 212 213 JsonValue markerNextValue = pageBody.get(BODY_PARAMETER_MARKER_NEXT); 214 if (markerNextValue != null && markerNextValue.isString()) { 215 this.markerNext = markerNextValue.asString(); 216 } else { 217 this.markerNext = null; 218 } 219 220 this.page = pageBody.get(BODY_PARAMETER_ENTRIES).asArray(); 221 this.pageCursor = 0; 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public boolean hasNext() { 229 if (this.pageCursor < this.page.size()) { 230 return true; 231 } 232 if (this.markerNext == null || this.markerNext.isEmpty()) { 233 return false; 234 } 235 this.loadNextPage(); 236 return !this.page.isEmpty(); 237 } 238 239 /** 240 * {@inheritDoc} 241 */ 242 @Override 243 public T next() { 244 if (!this.hasNext()) { 245 throw new NoSuchElementException(); 246 } 247 248 JsonObject entry = this.page.get(this.pageCursor++).asObject(); 249 return BoxResourceIterable.this.factory(entry); 250 } 251 252 /** 253 * @throws UnsupportedOperationException 254 */ 255 @Override 256 public void remove() { 257 throw new UnsupportedOperationException(); 258 } 259 } 260 261}