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 ForgeRock AS. 015 */ 016 017package org.forgerock.http.routing; 018 019import java.util.List; 020 021import org.forgerock.http.Filter; 022import org.forgerock.http.header.AcceptApiVersionHeader; 023import org.forgerock.http.protocol.Request; 024import org.forgerock.http.util.Paths; 025import org.forgerock.services.context.Context; 026import org.forgerock.services.routing.RouteMatch; 027import org.forgerock.services.routing.RouteMatcher; 028 029/** 030 * A utility class that contains methods for creating route matchers. 031 */ 032public final class RouteMatchers { 033 034 private RouteMatchers() { 035 // Private utility constructor. 036 } 037 038 /** 039 * Creates a {@code RouteMatcher} instance that matches 040 * {@code ResourcePath}s with the provided {@literal mode} and 041 * {@literal template}. 042 * 043 * @param mode The routing mode. 044 * @param template The uri template. 045 * @return A {@code RouteMatcher} instance. 046 */ 047 public static RouteMatcher<List<String>> uriMatcher(RoutingMode mode, String template) { 048 return new UriRouteMatcher(mode, template); 049 } 050 051 /** 052 * Creates a {@code RouteMatcher} instance that matches {@code Request}s 053 * with the provided {@literal mode} and {@literal template}. 054 * 055 * @param mode The routing mode. 056 * @param template The uri template. 057 * @return A {@code RouteMatcher} instance. 058 */ 059 public static RouteMatcher<Request> requestUriMatcher(RoutingMode mode, String template) { 060 RouteMatcher<List<String>> delegate = uriMatcher(mode, template); 061 return new RequestUriRouteMatcher(delegate); 062 } 063 064 /** 065 * Creates a new {@code ResourceApiVersionBehaviourManager} which is responsibly 066 * for managing whether warning headers are returned and the default 067 * version behaviour when the {@literal Accept-API-Version} header is not 068 * present on the request. 069 * 070 * @return A new {@code ResourceApiVersionBehaviourManager}. 071 */ 072 public static ResourceApiVersionBehaviourManager newResourceApiVersionBehaviourManager() { 073 return new ResourceApiVersionBehaviourManagerImpl(); 074 } 075 076 /** 077 * Creates a {@code Filter} which MUST be placed, in the route, before any 078 * API Version routing takes place. 079 * 080 * <p>The filter will add the required {@code Context}s, default version 081 * behaviour and response headers.</p> 082 * 083 * @param behaviourManager A {@code ResourceApiVersionBehaviourManager} instance. 084 * @return A {@code Filter} instance. 085 */ 086 public static Filter resourceApiVersionContextFilter(ResourceApiVersionBehaviourManager behaviourManager) { 087 return new ResourceApiVersionRoutingFilter(behaviourManager); 088 } 089 090 /** 091 * Creates a {@code RouteMatcher} instance that matches the request 092 * resource API version with the provided {@literal version}. 093 * 094 * @param version The API version of the resource. 095 * @return A {@code RouteMatcher} instance. 096 */ 097 public static RouteMatcher<Version> resourceApiVersionMatcher(Version version) { 098 return new ResourceApiVersionRouteMatcher(version); 099 } 100 101 /** 102 * Creates a {@code RouteMatcher} instance that matches the request 103 * resource API version with the provided {@literal version}. 104 * 105 * @param version The API version of the resource. 106 * @return A {@code RouteMatcher} instance. 107 */ 108 public static RouteMatcher<Request> requestResourceApiVersionMatcher(Version version) { 109 return new RequestApiVersionRouteMatcher(resourceApiVersionMatcher(version)); 110 } 111 112 /** 113 * A CHF specific {@code RouteMatcher} which extracts the routable path 114 * from a {@code Request} and passes it as a {@code ResourcePath} to the 115 * common {@code ResourcePath} route matcher. 116 */ 117 private static final class RequestUriRouteMatcher extends RouteMatcher<Request> { 118 119 private final RouteMatcher<List<String>> delegate; 120 121 private RequestUriRouteMatcher(RouteMatcher<List<String>> delegate) { 122 this.delegate = delegate; 123 } 124 125 @Override 126 public RouteMatch evaluate(Context context, Request request) { 127 return delegate.evaluate(context, getRemainingRequestUri(context, request)); 128 } 129 130 @Override 131 public String toString() { 132 return delegate.toString(); 133 } 134 135 @Override 136 public boolean equals(Object o) { 137 if (this == o) { 138 return true; 139 } 140 if (!(o instanceof RequestUriRouteMatcher)) { 141 return false; 142 } 143 RequestUriRouteMatcher that = (RequestUriRouteMatcher) o; 144 return delegate.equals(that.delegate); 145 } 146 147 @Override 148 public int hashCode() { 149 return delegate.hashCode(); 150 } 151 } 152 153 static List<String> getRemainingRequestUri(Context context, Request request) { 154 List<String> path = request.getUri().getPathElements(); 155 if (context.containsContext(UriRouterContext.class)) { 156 List<String> matchedUri = Paths.getPathElements(context.asContext(UriRouterContext.class).getBaseUri()); 157 path = path.subList(matchedUri.size(), path.size()); 158 } 159 return path; 160 } 161 162 /** 163 * The default implementation of the {@code ResourceApiVersionBehaviourManager} interface. 164 */ 165 private static final class ResourceApiVersionBehaviourManagerImpl implements ResourceApiVersionBehaviourManager { 166 167 private boolean warningEnabled = true; 168 private DefaultVersionBehaviour defaultVersionBehaviour = DefaultVersionBehaviour.LATEST; 169 170 @Override 171 public void setWarningEnabled(boolean warningEnabled) { 172 this.warningEnabled = warningEnabled; 173 } 174 175 @Override 176 public boolean isWarningEnabled() { 177 return warningEnabled; 178 } 179 180 @Override 181 public void setDefaultVersionBehaviour(DefaultVersionBehaviour behaviour) { 182 this.defaultVersionBehaviour = behaviour; 183 } 184 185 @Override 186 public DefaultVersionBehaviour getDefaultVersionBehaviour() { 187 return defaultVersionBehaviour; 188 } 189 } 190 191 /** 192 * A CHF specific {@code RouteMatcher} which extracts the resource API 193 * version from a {@code Request} and passes it to the common 194 * {@code Version} route matcher. 195 */ 196 private static final class RequestApiVersionRouteMatcher extends RouteMatcher<Request> { 197 198 private final RouteMatcher<Version> delegate; 199 200 private RequestApiVersionRouteMatcher(RouteMatcher<Version> delegate) { 201 this.delegate = delegate; 202 } 203 204 @Override 205 public RouteMatch evaluate(Context context, Request request) { 206 AcceptApiVersionHeader apiVersionHeader; 207 try { 208 apiVersionHeader = AcceptApiVersionHeader.valueOf(request); 209 } catch (IllegalArgumentException ignored) { 210 // This exception is thrown when trying to parse an invalid 211 // Accept-API-Version header. null is returned to signify a bad 212 // request. 213 return null; 214 } 215 return delegate.evaluate(context, apiVersionHeader.getResourceVersion()); 216 } 217 218 @Override 219 public String toString() { 220 return delegate.toString(); 221 } 222 223 @Override 224 public boolean equals(Object o) { 225 if (this == o) { 226 return true; 227 } 228 if (!(o instanceof RequestApiVersionRouteMatcher)) { 229 return false; 230 } 231 RequestApiVersionRouteMatcher that = (RequestApiVersionRouteMatcher) o; 232 return delegate.equals(that.delegate); 233 } 234 235 @Override 236 public int hashCode() { 237 return delegate.hashCode(); 238 } 239 } 240}