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}