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.HashSet;
020import java.util.Map;
021import java.util.Set;
022import javax.portlet.PortletMode;
023import javax.portlet.PortletRequest;
024
025import org.springframework.beans.BeansException;
026import org.springframework.util.Assert;
027
028/**
029 * Implementation of the {@link org.springframework.web.portlet.HandlerMapping}
030 * interface to map from the current PortletMode and a request parameter to
031 * request handler beans. The mapping consists of two levels: first the
032 * PortletMode and then the parameter value. In order to be mapped,
033 * both elements must match the mapping definition.
034 *
035 * <p>This is a combination of the methods used in {@link PortletModeHandlerMapping PortletModeHandlerMapping}
036 * and {@link ParameterHandlerMapping ParameterHandlerMapping}.  Unlike
037 * those two classes, this mapping cannot be initialized with properties since it
038 * requires a two-level map.
039 *
040 * <p>The default name of the parameter is "action", but can be changed using
041 * {@link #setParameterName setParameterName()}.
042 *
043 * <p>By default, the same parameter value may not be used in two different portlet
044 * modes.  This is so that if the portal itself changes the portlet mode, the request
045 * will no longer be valid in the mapping.  This behavior can be changed with
046 * {@link #setAllowDuplicateParameters setAllowDupParameters()}.
047 *
048 * <p>The bean configuration for this mapping will look somthing like this:
049 *
050 * <pre class="code">
051 * &lt;bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping"&gt;
052 *   &lt;property name="portletModeParameterMap"&gt;
053 *     &lt;map&gt;
054 *       &lt;entry key="view"&gt; &lt;!-- portlet mode: view --&gt;
055 *         &lt;map&gt;
056 *           &lt;entry key="add"&gt;&lt;ref bean="addItemHandler"/&gt;&lt;/entry&gt;
057 *           &lt;entry key="edit"&gt;&lt;ref bean="editItemHandler"/&gt;&lt;/entry&gt;
058 *           &lt;entry key="delete"&gt;&lt;ref bean="deleteItemHandler"/&gt;&lt;/entry&gt;
059 *         &lt;/map&gt;
060 *       &lt;/entry&gt;
061 *       &lt;entry key="edit"&gt; &lt;!-- portlet mode: edit --&gt;
062 *         &lt;map&gt;
063 *           &lt;entry key="prefs"&gt;&lt;ref bean="preferencesHandler"/&gt;&lt;/entry&gt;
064 *           &lt;entry key="resetPrefs"&gt;&lt;ref bean="resetPreferencesHandler"/&gt;&lt;/entry&gt;
065 *         &lt;/map&gt;
066 *       &lt;/entry&gt;
067 *     &lt;/map&gt;
068 *   &lt;/property&gt;
069 * &lt;/bean&gt;</pre>
070 *
071 * <p>This mapping can be chained ahead of a {@link PortletModeHandlerMapping PortletModeHandlerMapping},
072 * which can then provide defaults for each mode and an overall default as well.
073 *
074 * <p>Thanks to Rainer Schmitz and Yujin Kim for suggesting this mapping strategy!
075 *
076 * @author John A. Lewis
077 * @author Juergen Hoeller
078 * @since 2.0
079 * @see ParameterMappingInterceptor
080 */
081public class PortletModeParameterHandlerMapping extends AbstractMapBasedHandlerMapping<PortletModeParameterLookupKey> {
082
083        /**
084         * Default request parameter name to use for mapping to handlers: "action".
085         */
086        public final static String DEFAULT_PARAMETER_NAME = "action";
087
088
089        private String parameterName = DEFAULT_PARAMETER_NAME;
090
091        private Map<String, Map<String, ?>> portletModeParameterMap;
092
093        private boolean allowDuplicateParameters = false;
094
095        private final Set<String> parametersUsed = new HashSet<String>();
096
097
098        /**
099         * Set the name of the parameter used for mapping to handlers.
100         * <p>Default is "action".
101         */
102        public void setParameterName(String parameterName) {
103                Assert.hasText(parameterName, "'parameterName' must not be empty");
104                this.parameterName = parameterName;
105        }
106
107        /**
108         * Set a Map with portlet mode names as keys and another Map as values.
109         * The sub-map has parameter names as keys and handler bean or bean names as values.
110         * <p>Convenient for population with bean references.
111         * @param portletModeParameterMap two-level map of portlet modes and parameters to handler beans
112         */
113        public void setPortletModeParameterMap(Map<String, Map<String, ?>> portletModeParameterMap) {
114                this.portletModeParameterMap = portletModeParameterMap;
115        }
116
117        /**
118         * Set whether to allow duplicate parameter values across different portlet modes.
119         * Default is "false".
120         * <p>Doing this is dangerous because the portlet mode can be changed by the
121         * portal itself and the only way to see that is a rerender of the portlet.
122         * If the same parameter value is legal in multiple modes, then a change in
123         * mode could result in a matched mapping that is not intended and the user
124         * could end up in a strange place in the application.
125         */
126        public void setAllowDuplicateParameters(boolean allowDuplicateParameters) {
127                this.allowDuplicateParameters = allowDuplicateParameters;
128        }
129
130
131        /**
132         * Calls the {@code registerHandlers} method in addition
133         * to the superclass's initialization.
134         * @see #registerHandlers
135         */
136        @Override
137        public void initApplicationContext() throws BeansException {
138                super.initApplicationContext();
139                registerHandlersByModeAndParameter(this.portletModeParameterMap);
140        }
141
142        /**
143         * Register all handlers specified in the Portlet mode map for the corresponding modes.
144         * @param portletModeParameterMap Map with mode names as keys and parameter Maps as values
145         */
146        protected void registerHandlersByModeAndParameter(Map<String, Map<String, ?>> portletModeParameterMap) {
147                Assert.notNull(portletModeParameterMap, "'portletModeParameterMap' must not be null");
148                for (Map.Entry<String, Map<String, ?>> entry : portletModeParameterMap.entrySet()) {
149                        PortletMode mode = new PortletMode(entry.getKey());
150                        registerHandler(mode, entry.getValue());
151                }
152        }
153
154        /**
155         * Register all handlers specified in the given parameter map.
156         * @param parameterMap Map with parameter names as keys and handler beans or bean names as values
157         */
158        protected void registerHandler(PortletMode mode, Map<String, ?> parameterMap) {
159                for (Map.Entry<String, ?> entry : parameterMap.entrySet()) {
160                        registerHandler(mode, entry.getKey(), entry.getValue());
161                }
162        }
163
164        /**
165         * Register the given handler instance for the given PortletMode and parameter value,
166         * under an appropriate lookup key.
167         * @param mode the PortletMode for which this mapping is valid
168         * @param parameter the parameter value to which this handler is mapped
169         * @param handler the handler instance bean
170         * @throws BeansException if the handler couldn't be registered
171         * @throws IllegalStateException if there is a conflicting handler registered
172         * @see #registerHandler(Object, Object)
173         */
174        protected void registerHandler(PortletMode mode, String parameter, Object handler)
175                        throws BeansException, IllegalStateException {
176
177                // Check for duplicate parameter values across all portlet modes.
178                if (!this.allowDuplicateParameters && this.parametersUsed.contains(parameter)) {
179                        throw new IllegalStateException(
180                                        "Duplicate entries for parameter [" + parameter + "] in different Portlet modes");
181                }
182                this.parametersUsed.add(parameter);
183
184                registerHandler(new PortletModeParameterLookupKey(mode, parameter), handler);
185        }
186
187        /**
188         * Returns a lookup key that combines the current PortletMode and the current
189         * value of the specified parameter.
190         * @see javax.portlet.PortletRequest#getPortletMode()
191         * @see #setParameterName
192         */
193        @Override
194        protected PortletModeParameterLookupKey getLookupKey(PortletRequest request) throws Exception {
195                PortletMode mode = request.getPortletMode();
196                String parameter = request.getParameter(this.parameterName);
197                return new PortletModeParameterLookupKey(mode, parameter);
198        }
199
200}