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 * Constructor. 057 * 058 * @param api 059 * the API connection to be used by the resource 060 * @param url 061 * to end-point with paging support 062 * @param limit 063 * the maximum number of items to return in a page 064 */ 065 public BoxResourceIterable(BoxAPIConnection api, URL url, int limit) { 066 this.api = api; 067 this.url = url; 068 this.limit = limit; 069 } 070 071 /** 072 * Factory to build a new instance for a received JSON item. 073 * 074 * @param jsonObject 075 * of the item 076 * @return the item instance 077 */ 078 protected abstract T factory(JsonObject jsonObject); 079 080 /** 081 * Builds internal read-only iterator over {@link BoxResource}-s. 082 * 083 * @return iterator implementation 084 * @see Iterable#iterator() 085 */ 086 @Override 087 public Iterator<T> iterator() { 088 return new IteratorImpl(); 089 } 090 091 /** 092 * Paging implementation. 093 */ 094 private class IteratorImpl implements Iterator<T> { 095 096 /** 097 * Base 64 encoded string that represents where the paging should being. It should be left blank to begin 098 * paging. 099 */ 100 private String markerNext; 101 102 /** 103 * Current loaded page. 104 */ 105 private JsonArray page; 106 107 /** 108 * Cursor within the page (index of a next item for read). 109 */ 110 private int pageCursor; 111 112 /** 113 * Constructor. 114 */ 115 IteratorImpl() { 116 this.loadNextPage(); 117 } 118 119 /** 120 * Loads next page. 121 */ 122 private void loadNextPage() { 123 String existingQuery = BoxResourceIterable.this.url.getQuery(); 124 QueryStringBuilder builder = new QueryStringBuilder(existingQuery); 125 builder.appendParam(PARAMETER_LIMIT, BoxResourceIterable.this.limit); 126 if (this.markerNext != null) { 127 builder.appendParam(PARAMETER_MARKER, this.markerNext); 128 } 129 130 URL url; 131 try { 132 url = builder.addToURL(BoxResourceIterable.this.url); 133 } catch (MalformedURLException e) { 134 throw new BoxAPIException("Couldn't append a query string to the provided URL."); 135 } 136 137 BoxAPIRequest request = new BoxAPIRequest(BoxResourceIterable.this.api, url, "GET"); 138 BoxJSONResponse response = (BoxJSONResponse) request.send(); 139 JsonObject pageBody = JsonObject.readFrom(response.getJSON()); 140 141 JsonValue markerNextValue = pageBody.get(BODY_PARAMETER_MARKER_NEXT); 142 if (markerNextValue != null && markerNextValue.isString()) { 143 this.markerNext = markerNextValue.asString(); 144 } else { 145 this.markerNext = null; 146 } 147 148 this.page = pageBody.get(BODY_PARAMETER_ENTRIES).asArray(); 149 this.pageCursor = 0; 150 } 151 152 /** 153 * {@inheritDoc} 154 */ 155 @Override 156 public boolean hasNext() { 157 if (this.pageCursor < this.page.size()) { 158 return true; 159 } 160 if (this.markerNext == null || this.markerNext.isEmpty()) { 161 return false; 162 } 163 this.loadNextPage(); 164 return !this.page.isEmpty(); 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 @Override 171 public T next() { 172 if (!this.hasNext()) { 173 throw new NoSuchElementException(); 174 } 175 176 JsonObject entry = this.page.get(this.pageCursor++).asObject(); 177 return BoxResourceIterable.this.factory(entry); 178 } 179 180 /** 181 * @throws UnsupportedOperationException 182 */ 183 @Override 184 public void remove() { 185 throw new UnsupportedOperationException(); 186 } 187 188 } 189 190}