001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2013-2015 ForgeRock AS.
015 */
016
017package org.forgerock.json.resource;
018
019import static org.forgerock.util.Reject.checkNotNull;
020
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.List;
024import java.util.concurrent.CopyOnWriteArrayList;
025
026import org.forgerock.services.context.Context;
027import org.forgerock.util.promise.Promise;
028
029/**
030 * A chain of filters terminated by a target request handler. The filter chain
031 * is thread safe and supports updates to the list of filters and the target
032 * request handler while actively processing requests.
033 */
034public final class FilterChain implements RequestHandler {
035    /*
036     * A request handler which represents the current position in the filter
037     * chain. Maintains a reference to the filter chain which was in use at the
038     * time when the cursor was created.
039     */
040    private final class Cursor implements RequestHandler {
041        private final int pos;
042        private final Filter[] snapshot;
043
044        private Cursor() {
045            this(filters.toArray(new Filter[0]), 0);
046        }
047
048        private Cursor(final Filter[] snapshot, final int pos) {
049            this.snapshot = snapshot;
050            this.pos = pos;
051        }
052
053        @Override
054        public Promise<ActionResponse, ResourceException> handleAction(final Context context,
055                final ActionRequest request) {
056            if (hasNext()) {
057                return get().filterAction(context, request, next());
058            } else {
059                return target.handleAction(context, request);
060            }
061        }
062
063        @Override
064        public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
065                final CreateRequest request) {
066            if (hasNext()) {
067                return get().filterCreate(context, request, next());
068            } else {
069                return target.handleCreate(context, request);
070            }
071        }
072
073        @Override
074        public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
075                final DeleteRequest request) {
076            if (hasNext()) {
077                return get().filterDelete(context, request, next());
078            } else {
079                return target.handleDelete(context, request);
080            }
081        }
082
083        @Override
084        public Promise<ResourceResponse, ResourceException> handlePatch(final Context context,
085                final PatchRequest request) {
086            if (hasNext()) {
087                return get().filterPatch(context, request, next());
088            } else {
089                return target.handlePatch(context, request);
090            }
091        }
092
093        @Override
094        public Promise<QueryResponse, ResourceException> handleQuery(final Context context,
095                final QueryRequest request, final QueryResourceHandler handler) {
096            if (hasNext()) {
097                return get().filterQuery(context, request, handler, next());
098            } else {
099                return target.handleQuery(context, request, handler);
100            }
101        }
102
103        @Override
104        public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
105                final ReadRequest request) {
106            if (hasNext()) {
107                return get().filterRead(context, request, next());
108            } else {
109                return target.handleRead(context, request);
110            }
111        }
112
113        @Override
114        public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
115                final UpdateRequest request) {
116            if (hasNext()) {
117                return get().filterUpdate(context, request, next());
118            } else {
119                return target.handleUpdate(context, request);
120            }
121        }
122
123        private Filter get() {
124            return snapshot[pos];
125        }
126
127        private boolean hasNext() {
128            return pos < snapshot.length;
129        }
130
131        private Cursor next() {
132            return new Cursor(snapshot, pos + 1);
133        }
134
135    }
136
137    private final List<Filter> filters = new CopyOnWriteArrayList<>();
138    private volatile RequestHandler target;
139
140    /**
141     * Creates an empty filter chain.
142     *
143     * @param target
144     *            The target request handler which will be invoked once
145     *            processing has reached the end of the filter chain.
146     */
147    public FilterChain(final RequestHandler target) {
148        this.target = checkNotNull(target, "Cannot create FilterChain with null target RequestHandler");
149    }
150
151    /**
152     * Creates a filter chain containing the provided list of filters.
153     *
154     * @param target
155     *            The target request handler which will be invoked once
156     *            processing has reached the end of the filter chain.
157     * @param filters
158     *            The list of filters to be processed before invoking the
159     *            target.
160     */
161    public FilterChain(final RequestHandler target, final Collection<Filter> filters) {
162        this.target = checkNotNull(target, "Cannot create FilterChain with null target RequestHandler");
163        this.filters.addAll(filters);
164    }
165
166    /**
167     * Creates a filter chain containing the provided list of filters.
168     *
169     * @param target
170     *            The target request handler which will be invoked once
171     *            processing has reached the end of the filter chain.
172     * @param filters
173     *            The list of filters to be processed before invoking the
174     *            target.
175     */
176    public FilterChain(final RequestHandler target, final Filter... filters) {
177        this.target = checkNotNull(target, "Cannot create FilterChain with null target RequestHandler");
178        this.filters.addAll(Arrays.asList(filters));
179    }
180
181    /**
182     * Returns a modifiable list containing the list of filters in this filter
183     * chain. Updates to the filter chain are thread safe and may be performed
184     * while the processing requests.
185     *
186     * @return A modifiable list containing the list of filters in this filter
187     *         chain.
188     */
189    public List<Filter> getFilters() {
190        return filters;
191    }
192
193    /**
194     * Returns the target request handler which will be invoked once processing
195     * has reached the end of the filter chain.
196     *
197     * @return The target request handler which will be invoked once processing
198     *         has reached the end of the filter chain.
199     */
200    public RequestHandler getTarget() {
201        return target;
202    }
203
204    @Override
205    public Promise<ActionResponse, ResourceException> handleAction(final Context context,
206            final ActionRequest request) {
207        return new Cursor().handleAction(context, request);
208    }
209
210    @Override
211    public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
212            final CreateRequest request) {
213        return new Cursor().handleCreate(context, request);
214    }
215
216    @Override
217    public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
218            final DeleteRequest request) {
219        return new Cursor().handleDelete(context, request);
220    }
221
222    @Override
223    public Promise<ResourceResponse, ResourceException> handlePatch(final Context context,
224            final PatchRequest request) {
225        return new Cursor().handlePatch(context, request);
226    }
227
228    @Override
229    public Promise<QueryResponse, ResourceException> handleQuery(final Context context,
230            final QueryRequest request, final QueryResourceHandler handler) {
231        return new Cursor().handleQuery(context, request, handler);
232    }
233
234    @Override
235    public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
236            final ReadRequest request) {
237        return new Cursor().handleRead(context, request);
238    }
239
240    @Override
241    public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
242            final UpdateRequest request) {
243        return new Cursor().handleUpdate(context, request);
244    }
245
246    /**
247     * Sets the target request handler which will be invoked once processing has
248     * reached the end of the filter chain. The target request handler may be
249     * updated while the processing requests.
250     *
251     * @param target
252     *            The target request handler which will be invoked once
253     *            processing has reached the end of the filter chain.
254     * @return This a reference to this filter chain.
255     */
256    public FilterChain setTarget(final RequestHandler target) {
257        this.target = checkNotNull(target, "Cannot set target RequestHandler to null value");
258        return this;
259    }
260
261    @Override
262    public String toString() {
263        final StringBuilder builder = new StringBuilder();
264        builder.append(filters.toString());
265        builder.append(" -> ");
266        builder.append(target.toString());
267        return builder.toString();
268    }
269
270}