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.handler;
018:
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import javax.servlet.http.HttpServletRequest;
025:
026: import org.springframework.beans.BeansException;
027: import org.springframework.util.AntPathMatcher;
028: import org.springframework.util.Assert;
029: import org.springframework.util.PathMatcher;
030: import org.springframework.web.servlet.HandlerMapping;
031: import org.springframework.web.util.UrlPathHelper;
032:
033: /**
034: * Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
035: * implementations. Provides infrastructure for mapping handlers to URLs and configurable
036: * URL lookup. For information on the latter, see "alwaysUseFullPath" property.
037: *
038: * <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
039: * various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
040: * both "/test" and "/team", "/test/*" matches all paths in the "/test" directory,
041: * "/test/**" matches all paths below "/test". For details, see the
042: * {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
043: *
044: * <p>Will search all path patterns to find the most exact match for the
045: * current request path. The most exact match is defined as the longest
046: * path pattern that matches the current request path.
047: *
048: * @author Juergen Hoeller
049: * @since 16.04.2003
050: * @see #setAlwaysUseFullPath
051: * @see #setUrlDecode
052: * @see org.springframework.util.AntPathMatcher
053: */
054: public abstract class AbstractUrlHandlerMapping extends
055: AbstractHandlerMapping {
056:
057: private UrlPathHelper urlPathHelper = new UrlPathHelper();
058:
059: private PathMatcher pathMatcher = new AntPathMatcher();
060:
061: private Object rootHandler;
062:
063: private boolean lazyInitHandlers = false;
064:
065: private final Map handlerMap = new HashMap();
066:
067: /**
068: * Set if URL lookup should always use the full path within the current servlet
069: * context. Else, the path within the current servlet mapping is used if applicable
070: * (that is, in the case of a ".../*" servlet mapping in web.xml).
071: * <p>Default is "false".
072: * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
073: */
074: public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
075: this .urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
076: }
077:
078: /**
079: * Set if context path and request URI should be URL-decoded. Both are returned
080: * <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
081: * <p>Uses either the request encoding or the default encoding according
082: * to the Servlet spec (ISO-8859-1).
083: * <p>Note: Setting this to "true" requires JDK 1.4 if the encoding differs
084: * from the VM's platform default encoding, as JDK 1.3's URLDecoder class
085: * does not offer a way to specify the encoding.
086: * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
087: */
088: public void setUrlDecode(boolean urlDecode) {
089: this .urlPathHelper.setUrlDecode(urlDecode);
090: }
091:
092: /**
093: * Set the UrlPathHelper to use for resolution of lookup paths.
094: * <p>Use this to override the default UrlPathHelper with a custom subclass,
095: * or to share common UrlPathHelper settings across multiple HandlerMappings
096: * and MethodNameResolvers.
097: * @see org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
098: */
099: public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
100: this .urlPathHelper = urlPathHelper;
101: }
102:
103: /**
104: * Set the PathMatcher implementation to use for matching URL paths
105: * against registered URL patterns. Default is AntPathMatcher.
106: * @see org.springframework.util.AntPathMatcher
107: */
108: public void setPathMatcher(PathMatcher pathMatcher) {
109: Assert.notNull(pathMatcher, "PathMatcher must not be null");
110: this .pathMatcher = pathMatcher;
111: }
112:
113: /**
114: * Set the root handler for this handler mapping, that is,
115: * the handler to be registered for the root path ("/").
116: * <p>Default is <code>null</code>, indicating no root handler.
117: */
118: public void setRootHandler(Object rootHandler) {
119: this .rootHandler = rootHandler;
120: }
121:
122: /**
123: * Return the root handler for this handler mapping (registered for "/"),
124: * or <code>null</code> if none.
125: */
126: public Object getRootHandler() {
127: return this .rootHandler;
128: }
129:
130: /**
131: * Set whether to lazily initialize handlers. Only applicable to
132: * singleton handlers, as prototypes are always lazily initialized.
133: * Default is "false", as eager initialization allows for more efficiency
134: * through referencing the controller objects directly.
135: * <p>If you want to allow your controllers to be lazily initialized,
136: * make them "lazy-init" and set this flag to true. Just making them
137: * "lazy-init" will not work, as they are initialized through the
138: * references from the handler mapping in this case.
139: */
140: public void setLazyInitHandlers(boolean lazyInitHandlers) {
141: this .lazyInitHandlers = lazyInitHandlers;
142: }
143:
144: /**
145: * Look up a handler for the URL path of the given request.
146: * @param request current HTTP request
147: * @return the handler instance, or <code>null</code> if none found
148: */
149: protected Object getHandlerInternal(HttpServletRequest request)
150: throws Exception {
151: String lookupPath = this .urlPathHelper
152: .getLookupPathForRequest(request);
153: if (logger.isDebugEnabled()) {
154: logger.debug("Looking up handler for [" + lookupPath + "]");
155: }
156: Object handler = lookupHandler(lookupPath, request);
157: if (handler == null) {
158: // We need to care for the default handler directly, since we need to
159: // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
160: if ("/".equals(lookupPath)) {
161: handler = getRootHandler();
162: }
163: if (handler == null) {
164: handler = getDefaultHandler();
165: }
166: if (handler != null) {
167: exposePathWithinMapping(lookupPath, request);
168: }
169: }
170: return handler;
171: }
172:
173: /**
174: * Look up a handler instance for the given URL path.
175: * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
176: * and various Ant-style pattern matches, e.g. a registered "/t*" matches
177: * both "/test" and "/team". For details, see the AntPathMatcher class.
178: * <p>Looks for the most exact pattern, where most exact is defined as
179: * the longest path pattern.
180: * @param urlPath URL the bean is mapped to
181: * @param request current HTTP request (to expose the path within the mapping to)
182: * @return the associated handler instance, or <code>null</code> if not found
183: * @see #exposePathWithinMapping
184: * @see org.springframework.util.AntPathMatcher
185: */
186: protected Object lookupHandler(String urlPath,
187: HttpServletRequest request) {
188: // Direct match?
189: Object handler = this .handlerMap.get(urlPath);
190: if (handler != null) {
191: exposePathWithinMapping(urlPath, request);
192: return handler;
193: }
194: // Pattern match?
195: String bestPathMatch = null;
196: for (Iterator it = this .handlerMap.keySet().iterator(); it
197: .hasNext();) {
198: String registeredPath = (String) it.next();
199: if (this .pathMatcher.match(registeredPath, urlPath)
200: && (bestPathMatch == null || bestPathMatch.length() <= registeredPath
201: .length())) {
202: bestPathMatch = registeredPath;
203: }
204: }
205: if (bestPathMatch != null) {
206: handler = this .handlerMap.get(bestPathMatch);
207: exposePathWithinMapping(this .pathMatcher
208: .extractPathWithinPattern(bestPathMatch, urlPath),
209: request);
210: }
211: return handler;
212: }
213:
214: /**
215: * Expose the path within the current mapping as request attribute.
216: * @param pathWithinMapping the path within the current mapping
217: * @param request the request to expose the path to
218: * @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
219: */
220: protected void exposePathWithinMapping(String pathWithinMapping,
221: HttpServletRequest request) {
222: request.setAttribute(
223: HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
224: pathWithinMapping);
225: }
226:
227: /**
228: * Register the specified handler for the given URL paths.
229: * @param urlPaths the URLs that the bean should be mapped to
230: * @param beanName the name of the handler bean
231: * @throws BeansException if the handler couldn't be registered
232: * @throws IllegalStateException if there is a conflicting handler registered
233: */
234: protected void registerHandler(String[] urlPaths, String beanName)
235: throws BeansException, IllegalStateException {
236: Assert.notNull(urlPaths, "URL path array must not be null");
237: for (int j = 0; j < urlPaths.length; j++) {
238: registerHandler(urlPaths[j], beanName);
239: }
240: }
241:
242: /**
243: * Register the specified handler for the given URL path.
244: * @param urlPath the URL the bean should be mapped to
245: * @param handler the handler instance or handler bean name String
246: * (a bean name will automatically be resolved into the corrresponding handler bean)
247: * @throws BeansException if the handler couldn't be registered
248: * @throws IllegalStateException if there is a conflicting handler registered
249: */
250: protected void registerHandler(String urlPath, Object handler)
251: throws BeansException, IllegalStateException {
252: Assert.notNull(urlPath, "URL path must not be null");
253: Assert.notNull(handler, "Handler object must not be null");
254:
255: Object mappedHandler = this .handlerMap.get(urlPath);
256: if (mappedHandler != null) {
257: throw new IllegalStateException("Cannot map handler ["
258: + handler + "] to URL path [" + urlPath
259: + "]: There is already handler [" + mappedHandler
260: + "] mapped.");
261: }
262:
263: // Eagerly resolve handler if referencing singleton via name.
264: if (!this .lazyInitHandlers && handler instanceof String) {
265: String handlerName = (String) handler;
266: if (getApplicationContext().isSingleton(handlerName)) {
267: handler = getApplicationContext().getBean(handlerName);
268: }
269: }
270:
271: if (urlPath.equals("/")) {
272: if (logger.isDebugEnabled()) {
273: logger.debug("Root mapping to handler [" + handler
274: + "]");
275: }
276: setRootHandler(handler);
277: } else if (urlPath.equals("/*")) {
278: if (logger.isDebugEnabled()) {
279: logger.debug("Default mapping to handler [" + handler
280: + "]");
281: }
282: setDefaultHandler(handler);
283: } else {
284: this .handlerMap.put(urlPath, handler);
285: if (logger.isDebugEnabled()) {
286: logger.debug("Mapped URL path [" + urlPath
287: + "] onto handler [" + handler + "]");
288: }
289: }
290: }
291:
292: /**
293: * Return the registered handlers as an unmodifiable Map, with the registered path
294: * as key and the handler object (or handler bean name in case of a lazy-init handler)
295: * as value.
296: * @see #getDefaultHandler()
297: */
298: public final Map getHandlerMap() {
299: return Collections.unmodifiableMap(this.handlerMap);
300: }
301:
302: }
|