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 2012-2015 ForgeRock AS.
015 */
016
017package org.forgerock.http.routing;
018
019import static java.lang.String.format;
020import static org.forgerock.util.Reject.checkNotNull;
021
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.util.Collections;
025import java.util.Map;
026
027import org.forgerock.services.context.Context;
028import org.forgerock.services.context.AbstractContext;
029import org.forgerock.json.JsonValue;
030import org.forgerock.util.Reject;
031
032/**
033 * A {@link Context} which is created when a request has been routed. The
034 * context includes:
035 * <ul>
036 * <li>the portion of the request URI which matched the URI template
037 * <li>the portion of the request URI which is remaining to be matched</li>
038 * <li>a method for obtaining the base URI, which represents the portion of the
039 * request URI which has been routed so far. This is obtained dynamically by
040 * concatenating the matched URI with matched URIs in parent router contexts
041 * <li>a map which contains the parsed URI template variables, keyed on the URI
042 * template variable name.
043 * </ul>
044 */
045public final class UriRouterContext extends AbstractContext {
046
047    // Persisted attribute names
048    private static final String ATTR_MATCHED_URI = "matchedUri";
049    private static final String ATTR_REMAINIG_URI = "remainingUri";
050    private static final String ATTR_URI_TEMPLATE_VARIABLES = "uriTemplateVariables";
051    private static final String ATTR_ORIGINAL_URI = "originalUri";
052
053    private final Map<String, String> uriTemplateVariables;
054
055    /**
056     * The original message's URI, as received by the web container. This value is set by the receiving servlet and
057     * is immutable.
058     */
059    private URI originalUri;
060
061        /**
062     * Creates a new routing context having the provided parent, URI template
063     * variables, and an ID automatically generated using
064     * {@code UUID.randomUUID()}.
065     *
066     * @param parent
067     *            The parent server context.
068     * @param matchedUri
069     *            The matched URI
070     * @param remainingUri
071     *            The remaining URI to be matched.
072     * @param uriTemplateVariables
073     *            A {@code Map} containing the parsed URI template variables,
074     *            keyed on the URI template variable name.
075     */
076    public UriRouterContext(final Context parent, final String matchedUri, final String remainingUri,
077            final Map<String, String> uriTemplateVariables) {
078        this(parent, matchedUri, remainingUri, uriTemplateVariables, null);
079    }
080
081    /**
082     * Creates a new routing context having the provided parent, URI template
083     * variables, and an ID automatically generated using
084     * {@code UUID.randomUUID()}.
085     *
086     * @param parent
087     *            The parent server context.
088     * @param matchedUri
089     *            The matched URI
090     * @param remainingUri
091     *            The remaining URI to be matched.
092     * @param uriTemplateVariables
093     *            A {@code Map} containing the parsed URI template variables,
094     *            keyed on the URI template variable name.
095     * @param originalUri
096     *            The original URI
097     */
098    public UriRouterContext(final Context parent, final String matchedUri, final String remainingUri,
099            final Map<String, String> uriTemplateVariables, URI originalUri) {
100        super(checkNotNull(parent, "Cannot instantiate UriRouterContext with null parent Context"), "router");
101        data.put(ATTR_MATCHED_URI, matchedUri);
102        data.put(ATTR_REMAINIG_URI, remainingUri);
103        this.uriTemplateVariables = Collections.unmodifiableMap(uriTemplateVariables);
104        data.put(ATTR_URI_TEMPLATE_VARIABLES, this.uriTemplateVariables);
105
106        if (originalUri != null) {
107            if (parent.containsContext(UriRouterContext.class)) {
108                UriRouterContext parentUriRouterContext = parent.asContext(UriRouterContext.class);
109                Reject.ifTrue(parentUriRouterContext.getOriginalUri() != null, "Cannot set the originalUri more than "
110                        + "once in the chain.");
111            }
112            this.originalUri = originalUri;
113            data.put(ATTR_ORIGINAL_URI, originalUri.toASCIIString());
114        }
115    }
116
117    /**
118     * Restore from JSON representation.
119     *
120     * @param savedContext
121     *            The JSON representation from which this context's attributes
122     *            should be parsed.
123     * @param classLoader
124     *            The ClassLoader which can properly resolve the persisted class-name.
125     */
126    public UriRouterContext(final JsonValue savedContext, final ClassLoader classLoader) {
127        super(savedContext, classLoader);
128        this.uriTemplateVariables =
129                Collections.unmodifiableMap(data.get(ATTR_URI_TEMPLATE_VARIABLES).required().asMap(String.class));
130
131        final String savedUri = data.get(ATTR_ORIGINAL_URI).asString();
132        try {
133            this.originalUri = new URI(savedUri);
134        } catch (URISyntaxException ex) {
135            throw new IllegalArgumentException(format("The URI %s is not valid", savedUri));
136        }
137    }
138
139    /**
140     * Returns the portion of the request URI which has been routed so far. This
141     * is obtained dynamically by concatenating the matched URI with the base
142     * URI of the parent router context if present. The base URI is never
143     * {@code null} but may be "" (empty string).
144     *
145     * @return The non-{@code null} portion of the request URI which has been
146     *         routed so far.
147     */
148    public String getBaseUri() {
149        final StringBuilder builder = new StringBuilder();
150        final Context parent = getParent();
151        if (parent.containsContext(UriRouterContext.class)) {
152            final String baseUri = parent.asContext(UriRouterContext.class).getBaseUri();
153            if (!baseUri.isEmpty()) {
154                builder.append(baseUri);
155            }
156        }
157        final String matchedUri = getMatchedUri();
158        if (matchedUri.length() > 0) {
159            if (builder.length() > 0) {
160                builder.append('/');
161            }
162            builder.append(matchedUri);
163        }
164        return builder.toString();
165    }
166
167    /**
168     * Returns the portion of the request URI which matched the URI template.
169     * The matched URI is never {@code null} but may be "" (empty string).
170     *
171     * @return The non-{@code null} portion of the request URI which matched the
172     *         URI template.
173     */
174    public String getMatchedUri() {
175        return data.get(ATTR_MATCHED_URI).asString();
176    }
177
178    /**
179     * Returns the portion of the request URI which is remaining to be matched
180     * be the next router. The remaining URI is never {@code null} but may be
181     * "" (empty string).
182     *
183     * @return The non-{@code null} portion of the request URI which is
184     * remaining to be matched.
185     */
186    public String getRemainingUri() {
187        return data.get(ATTR_REMAINIG_URI).asString();
188    }
189
190    /**
191     * Returns an unmodifiable {@code Map} containing the parsed URI template
192     * variables, keyed on the URI template variable name.
193     *
194     * @return The unmodifiable {@code Map} containing the parsed URI template
195     *         variables, keyed on the URI template variable name.
196     */
197    public Map<String, String> getUriTemplateVariables() {
198        return uriTemplateVariables;
199    }
200
201    /**
202     * Get the original URI.
203     *
204     * @return The original URI
205     */
206    public URI getOriginalUri() {
207        final Context parent = getParent();
208
209        if (this.originalUri != null) {
210            return this.originalUri;
211        } else if (parent.containsContext(UriRouterContext.class)) {
212            return parent.asContext(UriRouterContext.class).getOriginalUri();
213        } else {
214            return null;
215        }
216    }
217}