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 java.util.Arrays;
020import java.util.Collection;
021import java.util.EnumSet;
022import java.util.Set;
023import java.util.regex.Pattern;
024
025import org.forgerock.services.context.Context;
026import org.forgerock.util.promise.Promise;
027
028/**
029 * This class contains methods for creating various kinds of {@code Filter} and
030 * {@code FilterCondition}s.
031 */
032public final class Filters {
033
034    /**
035     * A filter which invokes a sub-filter if a condition matches the request.
036     */
037    private static final class ConditionalFilter implements Filter {
038        private final FilterCondition condition;
039        private final Filter subFilter;
040
041        private ConditionalFilter(final FilterCondition condition, final Filter filter) {
042            this.condition = condition;
043            this.subFilter = filter;
044        }
045
046        @Override
047        public Promise<ActionResponse, ResourceException> filterAction(final Context context,
048                final ActionRequest request, final RequestHandler next) {
049            if (condition.matches(context, request)) {
050                return subFilter.filterAction(context, request, next);
051            } else {
052                return next.handleAction(context, request);
053            }
054        }
055
056        @Override
057        public Promise<ResourceResponse, ResourceException> filterCreate(final Context context,
058                final CreateRequest request, final RequestHandler next) {
059            if (condition.matches(context, request)) {
060                return subFilter.filterCreate(context, request, next);
061            } else {
062                return next.handleCreate(context, request);
063            }
064        }
065
066        @Override
067        public Promise<ResourceResponse, ResourceException> filterDelete(final Context context,
068                final DeleteRequest request, final RequestHandler next) {
069            if (condition.matches(context, request)) {
070                return subFilter.filterDelete(context, request, next);
071            } else {
072                return next.handleDelete(context, request);
073            }
074        }
075
076        @Override
077        public Promise<ResourceResponse, ResourceException> filterPatch(final Context context,
078                final PatchRequest request, final RequestHandler next) {
079            if (condition.matches(context, request)) {
080                return subFilter.filterPatch(context, request, next);
081            } else {
082                return next.handlePatch(context, request);
083            }
084        }
085
086        @Override
087        public Promise<QueryResponse, ResourceException> filterQuery(final Context context,
088                final QueryRequest request, final QueryResourceHandler handler, final RequestHandler next) {
089            if (condition.matches(context, request)) {
090                return subFilter.filterQuery(context, request, handler, next);
091            } else {
092                return next.handleQuery(context, request, handler);
093            }
094        }
095
096        @Override
097        public Promise<ResourceResponse, ResourceException> filterRead(final Context context, final ReadRequest request,
098                final RequestHandler next) {
099            if (condition.matches(context, request)) {
100                return subFilter.filterRead(context, request, next);
101            } else {
102                return next.handleRead(context, request);
103            }
104        }
105
106        @Override
107        public Promise<ResourceResponse, ResourceException> filterUpdate(final Context context,
108                final UpdateRequest request, final RequestHandler next) {
109            if (condition.matches(context, request)) {
110                return subFilter.filterUpdate(context, request, next);
111            } else {
112                return next.handleUpdate(context, request);
113            }
114        }
115    }
116
117    /**
118     * Returns a {@code FilterCondition} which will only match requests which
119     * match all the provided conditions.
120     *
121     * @param conditions
122     *            The conditions which requests must match.
123     * @return The filter condition.
124     */
125    public static FilterCondition and(final Collection<FilterCondition> conditions) {
126        return new FilterCondition() {
127            @Override
128            public boolean matches(final Context context, final Request request) {
129                for (final FilterCondition condition : conditions) {
130                    if (!condition.matches(context, request)) {
131                        return false;
132                    }
133                }
134                return true;
135            }
136        };
137    }
138
139    /**
140     * Returns a {@code FilterCondition} which will only match requests which
141     * match all the provided conditions.
142     *
143     * @param conditions
144     *            The conditions which requests must match.
145     * @return The filter condition.
146     */
147    public static FilterCondition and(final FilterCondition... conditions) {
148        return and(Arrays.asList(conditions));
149    }
150
151    /**
152     * Returns a {@code Filter} which will only invoke {@code subFilter} when
153     * the provided filter condition matches the request being processed.
154     *
155     * @param condition
156     *            The filter condition.
157     * @param subFilter
158     *            The sub-filter to be invoked when the condition matches.
159     * @return The wrapped filter.
160     */
161    public static Filter conditionalFilter(final FilterCondition condition, final Filter subFilter) {
162        return new ConditionalFilter(condition, subFilter);
163    }
164
165    /**
166     * Returns a {@code FilterCondition} which will only match requests whose
167     * type is contained in {@code types}.
168     *
169     * @param types
170     *            The request types which should be handled by the filter.
171     * @return The filter condition.
172     * @see Request#getRequestType()
173     */
174    public static FilterCondition matchRequestType(final RequestType... types) {
175        return matchRequestType(EnumSet.copyOf(Arrays.asList(types)));
176    }
177
178    /**
179     * Returns a {@code FilterCondition} which will only match requests whose
180     * type is contained in {@code types}.
181     *
182     * @param types
183     *            The request types which should be handled by the filter.
184     * @return The filter condition.
185     * @see Request#getRequestType()
186     */
187    public static FilterCondition matchRequestType(final Set<RequestType> types) {
188        return new FilterCondition() {
189            @Override
190            public boolean matches(final Context context, final Request request) {
191                return types.contains(request.getRequestType());
192            }
193        };
194    }
195
196    /**
197     * Returns a {@code FilterCondition} which will only match requests whose
198     * resource path matches the provided regular expression.
199     *
200     * @param regex
201     *            The regular expression which must match a request's resource
202     *            path.
203     * @return The filter condition.
204     * @see Request#getResourcePath()
205     */
206    public static FilterCondition matchResourcePath(final Pattern regex) {
207        return new FilterCondition() {
208            @Override
209            public boolean matches(final Context context, final Request request) {
210                return regex.matcher(request.getResourcePath()).matches();
211            }
212        };
213    }
214
215    /**
216     * Returns a {@code FilterCondition} which will only match requests whose
217     * resource path matches the provided regular expression.
218     *
219     * @param regex
220     *            The regular expression which must match a request's resource
221     *            path.
222     * @return The filter condition.
223     * @see Request#getResourcePath()
224     */
225    public static FilterCondition matchResourcePath(final String regex) {
226        return matchResourcePath(Pattern.compile(regex));
227    }
228
229    /**
230     * Returns a {@code FilterCondition} which will match requests which do not
231     * match the provided condition.
232     *
233     * @param condition
234     *            The condition which requests must not match.
235     * @return The filter condition.
236     */
237    public static FilterCondition not(final FilterCondition condition) {
238        return new FilterCondition() {
239            @Override
240            public boolean matches(final Context context, final Request request) {
241                return !condition.matches(context, request);
242            }
243        };
244    }
245
246    /**
247     * Returns a {@code FilterCondition} which will match requests which match
248     * any of the provided conditions.
249     *
250     * @param conditions
251     *            The conditions which requests may match.
252     * @return The filter condition.
253     */
254    public static FilterCondition or(final Collection<FilterCondition> conditions) {
255        return new FilterCondition() {
256            @Override
257            public boolean matches(final Context context, final Request request) {
258                for (final FilterCondition condition : conditions) {
259                    if (condition.matches(context, request)) {
260                        return true;
261                    }
262                }
263                return false;
264            }
265        };
266    }
267
268    /**
269     * Returns a {@code FilterCondition} which will match requests which match
270     * any of the provided conditions.
271     *
272     * @param conditions
273     *            The conditions which requests may match.
274     * @return The filter condition.
275     */
276    public static FilterCondition or(final FilterCondition... conditions) {
277        return or(Arrays.asList(conditions));
278    }
279
280    // Prevent instantiation.
281    private Filters() {
282        // Nothing to do.
283    }
284
285}