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 * <bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping"> 052 * <property name="portletModeParameterMap"> 053 * <map> 054 * <entry key="view"> <!-- portlet mode: view --> 055 * <map> 056 * <entry key="add"><ref bean="addItemHandler"/></entry> 057 * <entry key="edit"><ref bean="editItemHandler"/></entry> 058 * <entry key="delete"><ref bean="deleteItemHandler"/></entry> 059 * </map> 060 * </entry> 061 * <entry key="edit"> <!-- portlet mode: edit --> 062 * <map> 063 * <entry key="prefs"><ref bean="preferencesHandler"/></entry> 064 * <entry key="resetPrefs"><ref bean="resetPreferencesHandler"/></entry> 065 * </map> 066 * </entry> 067 * </map> 068 * </property> 069 * </bean></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}