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}