/* * Copyright 2002-2008 the original author or authors. * * 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.springframework.web.servlet.mvc.support; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.springframework.core.JdkVersion; import org.springframework.util.ClassUtils; import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping; /** * Base class for {@link org.springframework.web.servlet.HandlerMapping} implementations * that derive URL paths according to conventions for specific controller types. * * @author Juergen Hoeller * @since 2.5.3 * @see ControllerClassNameHandlerMapping * @see ControllerBeanNameHandlerMapping */ public abstract class AbstractControllerUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { private static final String ANNOTATION_PREDICATE_NAME = "org.springframework.web.servlet.mvc.support.AnnotationControllerTypePredicate"; private ControllerTypePredicate predicate; private Set excludedPackages = Collections.singleton("org.springframework.web.servlet.mvc"); private Set excludedClasses = Collections.EMPTY_SET; /** * Activates detection of annotated controllers when running on JDK 1.5 or higher. */ public AbstractControllerUrlHandlerMapping() { this.predicate = (JdkVersion.isAtLeastJava15() ? instantiateAnnotationPredicate() : new ControllerTypePredicate()); } /** * Set whether to activate or deactivate detection of annotated controllers. * <p>Annotated controllers will by included by default when runnong on JDK 1.5 or higher. */ public void setIncludeAnnotatedControllers(boolean includeAnnotatedControllers) { this.predicate = (includeAnnotatedControllers ? instantiateAnnotationPredicate() : new ControllerTypePredicate()); } /** * Specify Java packages that should be excluded from this mapping. * Any classes in such a package (or any of its subpackages) will be * ignored by this HandlerMapping. * <p>Default is to exclude the entire "org.springframework.web.servlet.mvc" * package, including its subpackages, since none of Spring's out-of-the-box * Controller implementations is a reasonable candidate for this mapping strategy. * Such controllers are typically handled by a separate HandlerMapping, * e.g. a {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}, * alongside this ControllerClassNameHandlerMapping for application controllers. */ public void setExcludedPackages(String[] excludedPackages) { this.excludedPackages = (excludedPackages != null ? new HashSet(Arrays.asList(excludedPackages)) : Collections.EMPTY_SET); } /** * Specify controller classes that should be excluded from this mapping. * Any such classes will simply be ignored by this HandlerMapping. */ public void setExcludedClasses(Class[] excludedClasses) { this.excludedClasses = (excludedClasses != null ? new HashSet(Arrays.asList(excludedClasses)) : Collections.EMPTY_SET); } private ControllerTypePredicate instantiateAnnotationPredicate() { try { return (ControllerTypePredicate) ClassUtils.forName(ANNOTATION_PREDICATE_NAME, AbstractControllerUrlHandlerMapping.class.getClassLoader()).newInstance(); } catch (Exception ex) { throw new IllegalStateException("Cannot load AnnotationControllerTypePredicate", ex); } } /** * This implementation delegates to {@link #buildUrlsForHandler}, * provided that {@link #isEligibleForMapping} returns <code>true</code>. */ protected String[] determineUrlsForHandler(String beanName) { Class beanClass = getApplicationContext().getType(beanName); if (isEligibleForMapping(beanName, beanClass)) { return buildUrlsForHandler(beanName, beanClass); } else { return null; } } /** * Determine whether the specified controller is excluded from this mapping. * @param beanName the name of the controller bean * @param beanClass the concrete class of the controller bean * @return whether the specified class is excluded * @see #setExcludedPackages * @see #setExcludedClasses */ protected boolean isEligibleForMapping(String beanName, Class beanClass) { if (beanClass == null) { if (logger.isDebugEnabled()) { logger.debug("Excluding controller bean '" + beanName + "' from class name mapping " + "because its bean type could not be determined"); } return false; } if (this.excludedClasses.contains(beanClass)) { if (logger.isDebugEnabled()) { logger.debug("Excluding controller bean '" + beanName + "' from class name mapping " + "because its bean class is explicitly excluded: " + beanClass.getName()); } return false; } String beanClassName = beanClass.getName(); for (Iterator it = this.excludedPackages.iterator(); it.hasNext();) { String packageName = (String) it.next(); if (beanClassName.startsWith(packageName)) { if (logger.isDebugEnabled()) { logger.debug("Excluding controller bean '" + beanName + "' from class name mapping " + "because its bean class is defined in an excluded package: " + beanClass.getName()); } return false; } } return isControllerType(beanClass); } /** * Determine whether the given bean class indicates a controller type * that is supported by this mapping strategy. * @param beanClass the class to introspect */ protected boolean isControllerType(Class beanClass) { return this.predicate.isControllerType(beanClass); } /** * Determine whether the given bean class indicates a controller type * that dispatches to multiple action methods. * @param beanClass the class to introspect */ protected boolean isMultiActionControllerType(Class beanClass) { return this.predicate.isMultiActionControllerType(beanClass); } /** * Abstract template method to be implemented by subclasses. * @param beanName the name of the bean * @param beanClass the type of the bean * @return the URLs determined for the bean */ protected abstract String[] buildUrlsForHandler(String beanName, Class beanClass); }