001/* 002 * Copyright 2002-2016 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.context; 018 019import java.util.Map; 020import java.util.concurrent.ConcurrentHashMap; 021import javax.portlet.PortletRequest; 022import javax.portlet.PortletResponse; 023import javax.portlet.PortletSession; 024 025import org.springframework.util.Assert; 026import org.springframework.util.StringUtils; 027import org.springframework.web.context.request.AbstractRequestAttributes; 028import org.springframework.web.context.request.DestructionCallbackBindingListener; 029import org.springframework.web.context.request.RequestAttributes; 030import org.springframework.web.portlet.util.PortletUtils; 031 032/** 033 * Portlet-based implementation of the 034 * {@link org.springframework.web.context.request.RequestAttributes} interface. 035 * 036 * <p>Accesses objects from portlet request and portlet session scope, 037 * with a distinction between "session" (the PortletSession's "portlet scope") 038 * and "global session" (the PortletSession's "application scope"). 039 * 040 * @author Juergen Hoeller 041 * @since 2.0 042 * @see javax.portlet.PortletRequest#getAttribute 043 * @see javax.portlet.PortletSession#getAttribute 044 * @see javax.portlet.PortletSession#PORTLET_SCOPE 045 * @see javax.portlet.PortletSession#APPLICATION_SCOPE 046 * @see RequestAttributes#SCOPE_SESSION 047 * @see RequestAttributes#SCOPE_GLOBAL_SESSION 048 */ 049public class PortletRequestAttributes extends AbstractRequestAttributes { 050 051 /** 052 * Constant identifying the {@link String} prefixed to the name of a 053 * destruction callback when it is stored in a {@link PortletSession}. 054 */ 055 public static final String DESTRUCTION_CALLBACK_NAME_PREFIX = 056 PortletRequestAttributes.class.getName() + ".DESTRUCTION_CALLBACK."; 057 058 059 private final PortletRequest request; 060 061 private PortletResponse response; 062 063 private volatile PortletSession session; 064 065 private final Map<String, Object> sessionAttributesToUpdate = new ConcurrentHashMap<String, Object>(1); 066 067 private final Map<String, Object> globalSessionAttributesToUpdate = new ConcurrentHashMap<String, Object>(1); 068 069 070 /** 071 * Create a new PortletRequestAttributes instance for the given request. 072 * @param request current portlet request 073 */ 074 public PortletRequestAttributes(PortletRequest request) { 075 Assert.notNull(request, "Request must not be null"); 076 this.request = request; 077 } 078 079 /** 080 * Create a new PortletRequestAttributes instance for the given request. 081 * @param request current portlet request 082 * @param response current portlet response (for optional exposure) 083 */ 084 public PortletRequestAttributes(PortletRequest request, PortletResponse response) { 085 this(request); 086 this.response = response; 087 } 088 089 090 /** 091 * Exposes the native {@link PortletRequest} that we're wrapping. 092 */ 093 public final PortletRequest getRequest() { 094 return this.request; 095 } 096 097 /** 098 * Exposes the native {@link PortletResponse} that we're wrapping (if any). 099 */ 100 public final PortletResponse getResponse() { 101 return this.response; 102 } 103 104 /** 105 * Exposes the {@link PortletSession} that we're wrapping. 106 * @param allowCreate whether to allow creation of a new session if none exists yet 107 */ 108 protected final PortletSession getSession(boolean allowCreate) { 109 if (isRequestActive()) { 110 PortletSession session = this.request.getPortletSession(allowCreate); 111 this.session = session; 112 return session; 113 } 114 else { 115 // Access through stored session reference, if any... 116 PortletSession session = this.session; 117 if (session == null) { 118 if (allowCreate) { 119 throw new IllegalStateException( 120 "No session found and request already completed - cannot create new session!"); 121 } 122 else { 123 session = this.request.getPortletSession(false); 124 this.session = session; 125 } 126 } 127 return session; 128 } 129 } 130 131 132 @Override 133 public Object getAttribute(String name, int scope) { 134 if (scope == SCOPE_REQUEST) { 135 if (!isRequestActive()) { 136 throw new IllegalStateException( 137 "Cannot ask for request attribute - request is not active anymore!"); 138 } 139 return this.request.getAttribute(name); 140 } 141 else { 142 PortletSession session = getSession(false); 143 if (session != null) { 144 if (scope == SCOPE_GLOBAL_SESSION) { 145 Object value = session.getAttribute(name, PortletSession.APPLICATION_SCOPE); 146 if (value != null) { 147 this.globalSessionAttributesToUpdate.put(name, value); 148 } 149 return value; 150 } 151 else { 152 Object value = session.getAttribute(name); 153 if (value != null) { 154 this.sessionAttributesToUpdate.put(name, value); 155 } 156 return value; 157 } 158 } 159 return null; 160 } 161 } 162 163 @Override 164 public void setAttribute(String name, Object value, int scope) { 165 if (scope == SCOPE_REQUEST) { 166 if (!isRequestActive()) { 167 throw new IllegalStateException( 168 "Cannot set request attribute - request is not active anymore!"); 169 } 170 this.request.setAttribute(name, value); 171 } 172 else { 173 PortletSession session = getSession(true); 174 if (scope == SCOPE_GLOBAL_SESSION) { 175 session.setAttribute(name, value, PortletSession.APPLICATION_SCOPE); 176 this.globalSessionAttributesToUpdate.remove(name); 177 } 178 else { 179 session.setAttribute(name, value); 180 this.sessionAttributesToUpdate.remove(name); 181 } 182 } 183 } 184 185 @Override 186 public void removeAttribute(String name, int scope) { 187 if (scope == SCOPE_REQUEST) { 188 if (isRequestActive()) { 189 this.request.removeAttribute(name); 190 removeRequestDestructionCallback(name); 191 } 192 } 193 else { 194 PortletSession session = getSession(false); 195 if (session != null) { 196 if (scope == SCOPE_GLOBAL_SESSION) { 197 session.removeAttribute(name, PortletSession.APPLICATION_SCOPE); 198 this.globalSessionAttributesToUpdate.remove(name); 199 } 200 else { 201 session.removeAttribute(name); 202 this.sessionAttributesToUpdate.remove(name); 203 } 204 } 205 } 206 } 207 208 @Override 209 public String[] getAttributeNames(int scope) { 210 if (scope == SCOPE_REQUEST) { 211 if (!isRequestActive()) { 212 throw new IllegalStateException( 213 "Cannot ask for request attributes - request is not active anymore!"); 214 } 215 return StringUtils.toStringArray(this.request.getAttributeNames()); 216 } 217 else { 218 PortletSession session = getSession(false); 219 if (session != null) { 220 if (scope == SCOPE_GLOBAL_SESSION) { 221 return StringUtils.toStringArray(session.getAttributeNames(PortletSession.APPLICATION_SCOPE)); 222 } 223 else { 224 return StringUtils.toStringArray(session.getAttributeNames()); 225 } 226 } 227 return new String[0]; 228 } 229 } 230 231 @Override 232 public void registerDestructionCallback(String name, Runnable callback, int scope) { 233 if (scope == SCOPE_REQUEST) { 234 registerRequestDestructionCallback(name, callback); 235 } 236 else { 237 registerSessionDestructionCallback(name, callback); 238 } 239 } 240 241 @Override 242 public Object resolveReference(String key) { 243 if (REFERENCE_REQUEST.equals(key)) { 244 return this.request; 245 } 246 else if (REFERENCE_SESSION.equals(key)) { 247 return getSession(true); 248 } 249 else { 250 return null; 251 } 252 } 253 254 @Override 255 public String getSessionId() { 256 return getSession(true).getId(); 257 } 258 259 @Override 260 public Object getSessionMutex() { 261 return PortletUtils.getSessionMutex(getSession(true)); 262 } 263 264 265 /** 266 * Update all accessed session attributes through {@code session.setAttribute} 267 * calls, explicitly indicating to the container that they might have been modified. 268 */ 269 @Override 270 protected void updateAccessedSessionAttributes() { 271 if (!this.sessionAttributesToUpdate.isEmpty() || !this.globalSessionAttributesToUpdate.isEmpty()) { 272 PortletSession session = getSession(false); 273 if (session != null) { 274 try { 275 for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) { 276 String name = entry.getKey(); 277 Object newValue = entry.getValue(); 278 Object oldValue = session.getAttribute(name); 279 if (oldValue == newValue) { 280 session.setAttribute(name, newValue); 281 } 282 } 283 for (Map.Entry<String, Object> entry : this.globalSessionAttributesToUpdate.entrySet()) { 284 String name = entry.getKey(); 285 Object newValue = entry.getValue(); 286 Object oldValue = session.getAttribute(name, PortletSession.APPLICATION_SCOPE); 287 if (oldValue == newValue) { 288 session.setAttribute(name, newValue, PortletSession.APPLICATION_SCOPE); 289 } 290 } 291 } 292 catch (IllegalStateException ex) { 293 // Session invalidated - shouldn't usually happen. 294 } 295 } 296 this.sessionAttributesToUpdate.clear(); 297 this.globalSessionAttributesToUpdate.clear(); 298 } 299 } 300 301 /** 302 * Register the given callback as to be executed after session termination. 303 * <p>Note: The callback object should be serializable in order to survive 304 * web app restarts. 305 * @param name the name of the attribute to register the callback for 306 * @param callback the callback to be executed for destruction 307 */ 308 protected void registerSessionDestructionCallback(String name, Runnable callback) { 309 PortletSession session = getSession(true); 310 session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name, 311 new DestructionCallbackBindingListener(callback)); 312 } 313 314 315 @Override 316 public String toString() { 317 return this.request.toString(); 318 } 319 320}