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}