/* * Copyright (c) 2001-2016, Inversoft Inc., All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.primeframework.mvc.action; import java.util.ArrayDeque; import java.util.Deque; import org.primeframework.mvc.PrimeException; import org.primeframework.mvc.action.config.ActionConfiguration; import org.primeframework.mvc.action.config.ActionConfigurationProvider; import org.primeframework.mvc.servlet.HTTPMethod; import org.primeframework.mvc.util.URITools; import com.google.inject.Inject; import com.google.inject.Injector; /** * This class is the default action mapper implementation. * * @author Brian Pontarelli */ public class DefaultActionMapper implements ActionMapper { private final ActionConfigurationProvider actionConfigurationProvider; private final Injector injector; @Inject public DefaultActionMapper(ActionConfigurationProvider actionConfigurationProvider, Injector injector) { this.actionConfigurationProvider = actionConfigurationProvider; this.injector = injector; } /** * {@inheritDoc} */ public ActionInvocation map(HTTPMethod httpMethod, String uri, boolean executeResult) { // Handle extensions String extension = URITools.determineExtension(uri); if (extension != null) { uri = uri.substring(0, uri.length() - extension.length() - 1); } ActionConfiguration actionConfiguration = actionConfigurationProvider.lookup(uri); if (actionConfiguration == null) { // Try the index cases. If the URI is /foo/, look for an action config of /foo/index and // use it. If the uri is /foo, look for a config of /foo/index and then send a redirect // to /foo/ if (uri.endsWith("/")) { actionConfiguration = actionConfigurationProvider.lookup(uri + "index"); } else { actionConfiguration = actionConfigurationProvider.lookup(uri + "/index"); if (actionConfiguration != null) { return new ActionInvocation(null, null, uri + "/", null, actionConfiguration); } } } // Okay, no index handling was found and there isn't anything yet, let's search for it, but // only if it isn't an index like URI (i.e. not /admin/) Deque<String> uriParameters = new ArrayDeque<>(); if (actionConfiguration == null && !uri.endsWith("/")) { int index = uri.lastIndexOf('/'); String localURI = uri; while (index > 0 && actionConfiguration == null) { // Add the restful parameter uriParameters.offerFirst(localURI.substring(index + 1)); // Check if this matches localURI = localURI.substring(0, index); actionConfiguration = actionConfigurationProvider.lookup(localURI); if (actionConfiguration != null && !canHandle(actionConfiguration, uri)) { actionConfiguration = null; } if (actionConfiguration == null) { index = localURI.lastIndexOf('/'); } } if (actionConfiguration == null) { uriParameters.clear(); } else { uri = localURI; } } // Create the action and find the method Object action = null; ExecuteMethodConfiguration method = null; if (actionConfiguration != null) { action = injector.getInstance(actionConfiguration.actionClass); // The action may be null if not implemented. method = actionConfiguration.executeMethods.get(httpMethod); } return new ActionInvocation(action, method, uri, extension, uriParameters, actionConfiguration, executeResult); } /** * Determines if the action configuration can handle the given URI. MVCConfiguration objects provide additional * handling for URI parameters and other cases and this method uses the full incoming URI to determine if the * configuration can handle it. * * @param actionConfiguration The action configuration to check. * @param uri The full incoming URI. * @return True if this configuration can handle the URI, false if not. */ protected boolean canHandle(ActionConfiguration actionConfiguration, String uri) { // Check if the URIs are equal if (actionConfiguration.uri.equals(uri)) { return true; } // Verify that the full URI starts with this URI if (!uri.startsWith(actionConfiguration.uri)) { return false; } String[] uriParts = uri.substring(actionConfiguration.uri.length() + 1).split("/"); for (int i = 0; i < uriParts.length; i++) { String uriPart = uriParts[i]; // If there are no more pattern parts, bail if (i >= actionConfiguration.patternParts.length) { break; } if (actionConfiguration.patternParts[i].startsWith("{*")) { // Bad pattern if (!actionConfiguration.patternParts[i].endsWith("}")) { throw new PrimeException("Action annotation in class [" + actionConfiguration.actionClass + "] contains an invalid URI parameter pattern [" + actionConfiguration.pattern + "]. A curly " + "bracket is unclosed. If you want to include a curly brakcet that is not " + "a URI parameter capture, you need to escape it like \\{"); } // Can't have wildcard capture in the middle if (i != actionConfiguration.patternParts.length - 1) { throw new PrimeException("Action annotation in class [" + actionConfiguration.actionClass + "] contains an invalid URI parameter pattern [" + actionConfiguration.pattern + "]. You cannot " + "have a wildcard capture (i.e. {*foo}) in the middle of the pattern. It must " + "be on the end of the pattern."); } break; } else if (actionConfiguration.patternParts[i].startsWith("{")) { if (!actionConfiguration.patternParts[i].endsWith("}")) { throw new PrimeException("Action annotation in class [" + actionConfiguration.actionClass + "] contains an invalid URI parameter pattern [" + actionConfiguration.pattern + "]. A curly " + "bracket is unclosed. If you want to include a curly brakcet that is not " + "a URI parameter capture, you need to escape it like \\{"); } } else { String patternPart = normalize(actionConfiguration.patternParts[i]); if (!uriPart.equals(patternPart)) { return false; } } } return true; } /** * Replaces \{ with { and \} with }. * * @param pattern The pattern to normalize. * @return The normalized pattern. */ protected String normalize(String pattern) { return pattern.replace("\\{", "{").replace("\\}", "}"); } }