001/* 002 * Copyright 2002-2012 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 * https://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 017package org.springframework.web.portlet.handler; 018 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.LinkedHashMap; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Map; 025import javax.portlet.PortletException; 026import javax.portlet.PortletRequest; 027 028import org.springframework.beans.BeansException; 029import org.springframework.util.Assert; 030 031/** 032 * Abstract base class for {@link org.springframework.web.portlet.HandlerMapping} 033 * implementations that rely on a map which caches handler objects per lookup key. 034 * Supports arbitrary lookup keys, and automatically resolves handler bean names 035 * into handler bean instances. 036 * 037 * @author Juergen Hoeller 038 * @since 2.0 039 * @see #getLookupKey(javax.portlet.PortletRequest) 040 * @see #registerHandler(Object, Object) 041 */ 042public abstract class AbstractMapBasedHandlerMapping<K> extends AbstractHandlerMapping { 043 044 private boolean lazyInitHandlers = false; 045 046 private final Map<K, Object> handlerMap = new HashMap<K, Object>(); 047 048 049 /** 050 * Set whether to lazily initialize handlers. Only applicable to 051 * singleton handlers, as prototypes are always lazily initialized. 052 * Default is false, as eager initialization allows for more efficiency 053 * through referencing the handler objects directly. 054 * <p>If you want to allow your handlers to be lazily initialized, 055 * make them "lazy-init" and set this flag to true. Just making them 056 * "lazy-init" will not work, as they are initialized through the 057 * references from the handler mapping in this case. 058 */ 059 public void setLazyInitHandlers(boolean lazyInitHandlers) { 060 this.lazyInitHandlers = lazyInitHandlers; 061 } 062 063 064 /** 065 * Determines a handler for the computed lookup key for the given request. 066 * @see #getLookupKey 067 */ 068 @Override 069 @SuppressWarnings("unchecked") 070 protected Object getHandlerInternal(PortletRequest request) throws Exception { 071 K lookupKey = getLookupKey(request); 072 Object handler = this.handlerMap.get(lookupKey); 073 if (handler != null && logger.isDebugEnabled()) { 074 logger.debug("Key [" + lookupKey + "] -> handler [" + handler + "]"); 075 } 076 if (handler instanceof Map) { 077 Map<PortletRequestMappingPredicate, Object> predicateMap = 078 (Map<PortletRequestMappingPredicate, Object>) handler; 079 List<PortletRequestMappingPredicate> filtered = new LinkedList<PortletRequestMappingPredicate>(); 080 for (PortletRequestMappingPredicate predicate : predicateMap.keySet()) { 081 if (predicate.match(request)) { 082 filtered.add(predicate); 083 } 084 } 085 if (filtered.isEmpty()) { 086 return null; 087 } 088 Collections.sort(filtered); 089 PortletRequestMappingPredicate predicate = filtered.get(0); 090 predicate.validate(request); 091 return predicateMap.get(predicate); 092 } 093 return handler; 094 } 095 096 /** 097 * Build a lookup key for the given request. 098 * @param request current portlet request 099 * @return the lookup key (never {@code null}) 100 * @throws Exception if key computation failed 101 */ 102 protected abstract K getLookupKey(PortletRequest request) throws Exception; 103 104 105 /** 106 * Register all handlers specified in the Portlet mode map for the corresponding modes. 107 * @param handlerMap Map with lookup keys as keys and handler beans or bean names as values 108 * @throws BeansException if the handler couldn't be registered 109 */ 110 protected void registerHandlers(Map<K, ?> handlerMap) throws BeansException { 111 Assert.notNull(handlerMap, "Handler Map must not be null"); 112 for (Map.Entry<K, ?> entry : handlerMap.entrySet()) { 113 registerHandler(entry.getKey(), entry.getValue()); 114 } 115 } 116 117 /** 118 * Register the given handler instance for the given parameter value. 119 * @param lookupKey the key to map the handler onto 120 * @param handler the handler instance or handler bean name String 121 * (a bean name will automatically be resolved into the corresponding handler bean) 122 * @throws BeansException if the handler couldn't be registered 123 * @throws IllegalStateException if there is a conflicting handler registered 124 */ 125 protected void registerHandler(K lookupKey, Object handler) throws BeansException, IllegalStateException { 126 registerHandler(lookupKey, handler, null); 127 } 128 129 /** 130 * Register the given handler instance for the given parameter value. 131 * @param lookupKey the key to map the handler onto 132 * @param handler the handler instance or handler bean name String 133 * (a bean name will automatically be resolved into the corresponding handler bean) 134 * @param predicate a predicate object for this handler (may be {@code null}), 135 * determining a match with the primary lookup key 136 * @throws BeansException if the handler couldn't be registered 137 * @throws IllegalStateException if there is a conflicting handler registered 138 */ 139 @SuppressWarnings("unchecked") 140 protected void registerHandler(K lookupKey, Object handler, PortletRequestMappingPredicate predicate) 141 throws BeansException, IllegalStateException { 142 143 Assert.notNull(lookupKey, "Lookup key must not be null"); 144 Assert.notNull(handler, "Handler object must not be null"); 145 Object resolvedHandler = handler; 146 147 // Eagerly resolve handler if referencing singleton via name. 148 if (!this.lazyInitHandlers && handler instanceof String) { 149 String handlerName = (String) handler; 150 if (getApplicationContext().isSingleton(handlerName)) { 151 resolvedHandler = getApplicationContext().getBean(handlerName); 152 } 153 } 154 155 // Check for duplicate mapping. 156 Object mappedHandler = this.handlerMap.get(lookupKey); 157 if (mappedHandler != null && !(mappedHandler instanceof Map)) { 158 if (mappedHandler != resolvedHandler) { 159 throw new IllegalStateException("Cannot map handler [" + handler + "] to key [" + lookupKey + 160 "]: There's already handler [" + mappedHandler + "] mapped."); 161 } 162 } 163 else { 164 if (predicate != null) { 165 // Add the handler to the predicate map. 166 Map<PortletRequestMappingPredicate, Object> predicateMap = 167 (Map<PortletRequestMappingPredicate, Object>) mappedHandler; 168 if (predicateMap == null) { 169 predicateMap = new LinkedHashMap<PortletRequestMappingPredicate, Object>(); 170 this.handlerMap.put(lookupKey, predicateMap); 171 } 172 predicateMap.put(predicate, resolvedHandler); 173 } 174 else { 175 // Add the single handler to the map. 176 this.handlerMap.put(lookupKey, resolvedHandler); 177 } 178 if (logger.isDebugEnabled()) { 179 logger.debug("Mapped key [" + lookupKey + "] onto handler [" + resolvedHandler + "]"); 180 } 181 } 182 } 183 184 185 /** 186 * Predicate interface for determining a match with a given request. 187 */ 188 protected interface PortletRequestMappingPredicate extends Comparable<PortletRequestMappingPredicate> { 189 190 /** 191 * Determine whether the given request matches this predicate. 192 * @param request current portlet request 193 */ 194 boolean match(PortletRequest request); 195 196 /** 197 * Validate this predicate's mapping against the current request. 198 * @param request current portlet request 199 * @throws PortletException if validation failed 200 */ 201 void validate(PortletRequest request) throws PortletException; 202 } 203 204}