001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.servlet.mvc.support;
018:
019: import java.util.Arrays;
020: import java.util.Collections;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.Set;
024:
025: import org.springframework.beans.BeansException;
026: import org.springframework.util.ClassUtils;
027: import org.springframework.web.servlet.HandlerMapping;
028: import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
029: import org.springframework.web.servlet.mvc.Controller;
030: import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
031: import org.springframework.web.servlet.mvc.throwaway.ThrowawayController;
032:
033: /**
034: * Implementation of {@link HandlerMapping} that follows a simple convention for
035: * generating URL path mappings from the class names of registered
036: * {@link org.springframework.web.servlet.mvc.Controller} and
037: * {@link org.springframework.web.servlet.mvc.throwaway.ThrowawayController} beans.
038: *
039: * <p>For simple {@link org.springframework.web.servlet.mvc.Controller} implementations
040: * (those that handle a single request type), the convention is to take the
041: * {@link ClassUtils#getShortName short name} of the <code>Class</code>,
042: * remove the 'Controller' suffix if it exists and return the remaining text, lowercased,
043: * as the mapping, with a leading <code>/</code>. For example:
044: * <ul>
045: * <li><code>WelcomeController</code> -> <code>/welcome*</code></li>
046: * <li><code>HomeController</code> -> <code>/home*</code></li>
047: * </ul>
048: *
049: * <p>For {@link MultiActionController MultiActionControllers} then a similar mapping is registered,
050: * except that all sub-paths are registed using the trailing wildcard pattern <code>/*</code>.
051: * For example:
052: * <ul>
053: * <li><code>WelcomeController</code> -> <code>/welcome/*</code></li>
054: * <li><code>CatalogController</code> -> <code>/catalog/*</code></li>
055: * </ul>
056: *
057: * <p>For {@link MultiActionController} it is often useful to use
058: * this mapping strategy in conjunction with the
059: * {@link org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver}.
060: *
061: * @author Rob Harrop
062: * @author Juergen Hoeller
063: * @since 2.0
064: * @see org.springframework.web.servlet.mvc.Controller
065: * @see org.springframework.web.servlet.mvc.throwaway.ThrowawayController
066: * @see org.springframework.web.servlet.mvc.multiaction.MultiActionController
067: */
068: public class ControllerClassNameHandlerMapping extends
069: AbstractUrlHandlerMapping implements HandlerMapping {
070:
071: /**
072: * Common suffix at the end of controller implementation classes.
073: * Removed when generating the URL path.
074: */
075: private static final String CONTROLLER_SUFFIX = "Controller";
076:
077: private Set excludedPackages = Collections
078: .singleton("org.springframework.web.servlet.mvc");
079:
080: private Set excludedClasses = Collections.EMPTY_SET;
081:
082: /**
083: * Specify Java packages that should be excluded from this mapping.
084: * Any classes in such a package (or any of its subpackages) will be
085: * ignored by this HandlerMapping.
086: * <p>Default is to exclude the entire "org.springframework.web.servlet.mvc"
087: * package, including its subpackages, since none of Spring's out-of-the-box
088: * Controller implementations is a reasonable candidate for this mapping strategy.
089: * Such controllers are typically handled by a separate HandlerMapping,
090: * e.g. a {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping},
091: * alongside this ControllerClassNameHandlerMapping for application controllers.
092: */
093: public void setExcludedPackages(String[] excludedPackages) {
094: this .excludedPackages = (excludedPackages != null ? new HashSet(
095: Arrays.asList(excludedPackages))
096: : Collections.EMPTY_SET);
097: }
098:
099: /**
100: * Specify controller classes that should be excluded from this mapping.
101: * Any such classes will simply be ignored by this HandlerMapping.
102: */
103: public void setExcludedClasses(Class[] excludedClasses) {
104: this .excludedClasses = (excludedClasses != null ? new HashSet(
105: Arrays.asList(excludedClasses)) : Collections.EMPTY_SET);
106: }
107:
108: /**
109: * Calls the {@link #detectControllers()} method in addition to the
110: * superclass's initialization.
111: */
112: protected void initApplicationContext() {
113: super .initApplicationContext();
114: detectControllers();
115: }
116:
117: /**
118: * Detect all the {@link org.springframework.web.servlet.mvc.Controller} and
119: * {@link org.springframework.web.servlet.mvc.throwaway.ThrowawayController}
120: * beans registered in the {@link org.springframework.context.ApplicationContext}
121: * and register a URL path mapping for each one based on rules defined here.
122: * @throws BeansException if the controllers couldn't be obtained or registered
123: * @see #generatePathMapping(Class)
124: */
125: protected void detectControllers() throws BeansException {
126: registerControllers(Controller.class);
127: registerControllers(ThrowawayController.class);
128: }
129:
130: /**
131: * Register all controllers of the given type, searching the current
132: * DispatcherServlet's ApplicationContext for matching beans.
133: * @param controllerType the type of controller to search for
134: * @throws BeansException if the controllers couldn't be obtained or registered
135: */
136: protected void registerControllers(Class controllerType)
137: throws BeansException {
138: String[] beanNames = getApplicationContext()
139: .getBeanNamesForType(controllerType);
140: for (int i = 0; i < beanNames.length; i++) {
141: String beanName = beanNames[i];
142: Class beanClass = getApplicationContext().getType(beanName);
143: if (isEligibleForMapping(beanName, beanClass)) {
144: registerController(beanName, beanClass);
145: }
146: }
147: }
148:
149: /**
150: * Determine whether the specified controller is excluded from this mapping.
151: * @param beanName the name of the controller bean
152: * @param beanClass the concrete class of the controller bean
153: * @return whether the specified class is excluded
154: * @see #setExcludedPackages
155: * @see #setExcludedClasses
156: */
157: protected boolean isEligibleForMapping(String beanName,
158: Class beanClass) {
159: if (beanClass == null) {
160: if (logger.isDebugEnabled()) {
161: logger
162: .debug("Excluding controller bean '"
163: + beanName
164: + "' from class name mapping "
165: + "because its bean type could not be determined");
166: }
167: return false;
168: }
169: if (this .excludedClasses.contains(beanClass)) {
170: if (logger.isDebugEnabled()) {
171: logger
172: .debug("Excluding controller bean '"
173: + beanName
174: + "' from class name mapping "
175: + "because its bean class is explicitly excluded: "
176: + beanClass.getName());
177: }
178: return false;
179: }
180: String beanClassName = beanClass.getName();
181: for (Iterator it = this .excludedPackages.iterator(); it
182: .hasNext();) {
183: String packageName = (String) it.next();
184: if (beanClassName.startsWith(packageName)) {
185: if (logger.isDebugEnabled()) {
186: logger
187: .debug("Excluding controller bean '"
188: + beanName
189: + "' from class name mapping "
190: + "because its bean class is defined in an excluded package: "
191: + beanClass.getName());
192: }
193: return false;
194: }
195: }
196: return true;
197: }
198:
199: /**
200: * Register the controller with the given name, as defined
201: * in the current application context.
202: * @param beanName the name of the controller bean
203: * @param beanClass the concrete class of the controller bean
204: * @throws BeansException if the controller couldn't be registered
205: * @throws IllegalStateException if there is a conflicting handler registered
206: * @see #getApplicationContext()
207: */
208: protected void registerController(String beanName, Class beanClass)
209: throws BeansException, IllegalStateException {
210: String urlPath = generatePathMapping(beanClass);
211: if (logger.isDebugEnabled()) {
212: logger.debug("Registering Controller '" + beanName
213: + "' as handler for URL path [" + urlPath + "]");
214: }
215: registerHandler(urlPath, beanName);
216: }
217:
218: /**
219: * Generate the actual URL path for the given controller class.
220: * <p>Subclasses may choose to customize the paths that are generated
221: * by overriding this method.
222: * @param beanClass the controller bean class to generate a mapping for
223: * @return the URL path mapping for the given controller
224: */
225: protected String generatePathMapping(Class beanClass) {
226: StringBuffer pathMapping = new StringBuffer("/");
227: String className = ClassUtils.getShortName(beanClass.getName());
228: String path = (className.endsWith(CONTROLLER_SUFFIX) ? className
229: .substring(0, className.indexOf(CONTROLLER_SUFFIX))
230: : className);
231: pathMapping.append(path.toLowerCase());
232: if (MultiActionController.class.isAssignableFrom(beanClass)) {
233: pathMapping.append("/*");
234: } else {
235: pathMapping.append("*");
236: }
237: return pathMapping.toString();
238: }
239:
240: }
|