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 2015-2016 ForgeRock AS.
015 */
016
017package org.forgerock.json.resource;
018
019import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionMatcher;
020import static org.forgerock.http.routing.RouteMatchers.uriMatcher;
021
022import java.util.ArrayList;
023import java.util.List;
024
025import org.forgerock.http.routing.ResourceApiVersionBehaviourManager;
026import org.forgerock.http.routing.RoutingMode;
027import org.forgerock.http.routing.Version;
028import org.forgerock.http.ApiProducer;
029import org.forgerock.services.context.Context;
030import org.forgerock.services.routing.IncomparableRouteMatchException;
031import org.forgerock.services.routing.RouteMatch;
032import org.forgerock.services.routing.RouteMatcher;
033
034/**
035 * A utility class that contains methods for creating route matchers.
036 */
037public final class RouteMatchers {
038
039    private static final SelfApiMatcher SELF_API_MATCHER = new SelfApiMatcher();
040
041    private RouteMatchers() {
042    }
043
044    /**
045     * Creates a {@code RouteMatcher} instance that matches {@code Request}s
046     * with the provided {@literal mode} and {@literal template}.
047     *
048     * @param mode The routing mode.
049     * @param template The uri template.
050     * @return A {@code RouteMatcher} instance.
051     */
052    public static RouteMatcher<Request> requestUriMatcher(RoutingMode mode, String template) {
053        return new RequestUriRouteMatcher(uriMatcher(mode, template));
054    }
055
056    /**
057     * Creates a new {@code ResourceApiVersionBehaviourManager} which is responsibly
058     * for managing whether warning headers are returned and the default
059     * version behaviour when the {@literal Accept-API-Version} header is not
060     * present on the request.
061     *
062     * @return A new {@code ResourceApiVersionBehaviourManager}.
063     */
064    public static ResourceApiVersionBehaviourManager newResourceApiVersionBehaviourManager() {
065        return org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager();
066    }
067
068    /**
069     * Creates a {@code Filter} which MUST be placed, in the route, before any
070     * API Version routing takes place.
071     *
072     * <p>The filter will add the required {@code Context}s, default version
073     * behaviour and response headers.</p>
074     *
075     * @param behaviourManager A {@code ResourceApiVersionBehaviourManager} instance.
076     * @return A {@code Filter} instance.
077     */
078    public static Filter resourceApiVersionContextFilter(ResourceApiVersionBehaviourManager behaviourManager) {
079        return new ResourceApiVersionRoutingFilter(behaviourManager);
080    }
081
082    /**
083     * Creates a {@code RouteMatcher} instance that matches the request
084     * resource API version with the provided {@literal version}.
085     *
086     * @param version The API version of the resource.
087     * @return A {@code RouteMatcher} instance.
088     */
089    public static RouteMatcher<Request> requestResourceApiVersionMatcher(Version version) {
090        return new RequestApiVersionRouteMatcher(resourceApiVersionMatcher(version));
091    }
092
093    /**
094     * A matcher to check if the request is for all versions of the API descriptor of the current path.
095     *
096     * @return A {@code RouteMatcher} instance.
097     */
098    static RouteMatcher<Request> selfApiMatcher() {
099        return SELF_API_MATCHER;
100    }
101
102    /**
103     * A CREST specific {@code RouteMatcher} which extracts the requests
104     * resource name from a {@code Request} and passes it as a
105     * {@code ResourcePath} to the common {@code ResourcePath} route predicate.
106     */
107    private static final class RequestUriRouteMatcher extends RouteMatcher<Request> {
108
109        private final RouteMatcher<List<String>> delegate;
110
111        private RequestUriRouteMatcher(RouteMatcher<List<String>> delegate) {
112            this.delegate = delegate;
113        }
114
115        @Override
116        public RouteMatch evaluate(Context context, Request request) {
117            final List<String> pathElements = new ArrayList<>(request.getResourcePathObject().size());
118            for (String pathElement : request.getResourcePathObject()) {
119                pathElements.add(pathElement);
120            }
121            return delegate.evaluate(context, pathElements);
122        }
123
124        @Override
125        public String toString() {
126            return delegate.toString();
127        }
128
129        @Override
130        public String idFragment() {
131            return delegate.idFragment();
132        }
133
134        @Override
135        public boolean equals(Object o) {
136            if (this == o) {
137                return true;
138            }
139            if (!(o instanceof RequestUriRouteMatcher)) {
140                return false;
141            }
142            RequestUriRouteMatcher that = (RequestUriRouteMatcher) o;
143            return delegate.equals(that.delegate);
144        }
145
146        @Override
147        public <T> T transformApi(T t, ApiProducer<T> apiProducer) {
148            return delegate.transformApi(t, apiProducer);
149        }
150
151        @Override
152        public int hashCode() {
153            return delegate.hashCode();
154        }
155    }
156
157    /**
158     * A CREST specific {@code RouteMatcher} which extracts the resource API
159     * version from a {@code Request} and passes it to the common
160     * {@code Version} route matcher.
161     */
162    private static final class RequestApiVersionRouteMatcher extends RouteMatcher<Request> {
163
164        private final RouteMatcher<Version> delegate;
165
166        private RequestApiVersionRouteMatcher(RouteMatcher<Version> delegate) {
167            this.delegate = delegate;
168        }
169
170        @Override
171        public RouteMatch evaluate(Context context, Request request) {
172            return delegate.evaluate(context, request.getResourceVersion());
173        }
174
175        @Override
176        public String toString() {
177            return delegate.toString();
178        }
179
180        @Override
181        public String idFragment() {
182            return delegate.idFragment();
183        }
184
185        @Override
186        public boolean equals(Object o) {
187            if (this == o) {
188                return true;
189            }
190            if (!(o instanceof RequestApiVersionRouteMatcher)) {
191                return false;
192            }
193            RequestApiVersionRouteMatcher that = (RequestApiVersionRouteMatcher) o;
194            return delegate.equals(that.delegate);
195        }
196
197        @Override
198        public <T> T transformApi(T t, ApiProducer<T> apiProducer) {
199            return delegate.transformApi(t, apiProducer);
200        }
201
202        @Override
203        public int hashCode() {
204            return delegate.hashCode();
205        }
206    }
207
208    private static class SelfApiMatcher extends RouteMatcher<Request> {
209
210        @Override
211        public RouteMatch evaluate(Context context, final Request request) {
212            return new RouteMatch() {
213                @Override
214                public boolean isBetterMatchThan(RouteMatch result) throws IncomparableRouteMatchException {
215                    return request.getRequestType().equals(RequestType.API)
216                            && request.getResourceVersion() == null
217                            && request.getResourcePathObject().equals(ResourcePath.empty());
218                }
219
220                @Override
221                public Context decorateContext(Context context) {
222                    return context;
223                }
224            };
225        }
226
227        @Override
228        public String toString() {
229            return "API Request";
230        }
231
232        @Override
233        public int hashCode() {
234            return 0;
235        }
236
237        @Override
238        public boolean equals(Object o) {
239            return this == o;
240        }
241
242        @Override
243        public String idFragment() {
244            throw new UnsupportedOperationException();
245        }
246
247        @Override
248        public <D> D transformApi(D descriptor, ApiProducer<D> producer) {
249            throw new UnsupportedOperationException();
250        }
251    }
252}