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