001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.session; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.Enumeration; 023import java.util.List; 024import java.util.Set; 025import java.util.concurrent.CopyOnWriteArraySet; 026 027import javax.servlet.http.HttpServletRequest; 028import javax.servlet.http.HttpSession; 029import javax.servlet.http.HttpSessionBindingEvent; 030import javax.servlet.http.HttpSessionBindingListener; 031 032import org.apache.wicket.Application; 033import org.apache.wicket.Session; 034import org.apache.wicket.markup.MarkupParser; 035import org.apache.wicket.protocol.http.IRequestLogger; 036import org.apache.wicket.protocol.http.WebApplication; 037import org.apache.wicket.request.Request; 038import org.apache.wicket.request.http.WebRequest; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042/** 043 * Implementation of {@link ISessionStore} that works with web applications and provides some 044 * specific http servlet/ session related functionality. 045 * 046 * @author jcompagner 047 * @author Eelco Hillenius 048 * @author Matej Knopp 049 */ 050public class HttpSessionStore implements ISessionStore 051{ 052 private static final Logger log = LoggerFactory.getLogger(HttpSessionStore.class); 053 054 private final Set<UnboundListener> unboundListeners = new CopyOnWriteArraySet<UnboundListener>(); 055 056 private final Set<BindListener> bindListeners = new CopyOnWriteArraySet<BindListener>(); 057 058 /** 059 * @param request The Wicket request 060 * @return The http servlet request 061 */ 062 protected final HttpServletRequest getHttpServletRequest(final Request request) 063 { 064 Object containerRequest = request.getContainerRequest(); 065 if (containerRequest == null || (containerRequest instanceof HttpServletRequest) == false) 066 { 067 throw new IllegalArgumentException("Request must be ServletWebRequest"); 068 } 069 return (HttpServletRequest)containerRequest; 070 } 071 072 /** 073 * 074 * @see HttpServletRequest#getSession(boolean) 075 * 076 * @param request 077 * A Wicket request object 078 * @param create 079 * If true, a session will be created if it is not existing yet 080 * @return The HttpSession associated with this request or null if {@code create} is false and 081 * the {@code request} has no valid session 082 */ 083 final HttpSession getHttpSession(final Request request, final boolean create) 084 { 085 return getHttpServletRequest(request).getSession(create); 086 } 087 088 @Override 089 public final void bind(final Request request, final Session newSession) 090 { 091 if (getWicketSession(request) != newSession) 092 { 093 // call template method 094 onBind(request, newSession); 095 for (BindListener listener : getBindListeners()) 096 { 097 listener.bindingSession(request, newSession); 098 } 099 100 HttpSession httpSession = getHttpSession(request, false); 101 102 if (httpSession != null) 103 { 104 // register an unbinding listener for cleaning up 105 String applicationKey = Application.get().getName(); 106 httpSession.setAttribute("Wicket:SessionUnbindingListener-" + applicationKey, 107 new SessionBindingListener(applicationKey, newSession)); 108 109 // register the session object itself 110 setWicketSession(request, newSession); 111 } 112 } 113 } 114 115 @Override 116 public void flushSession(Request request, Session session) 117 { 118 if (getWicketSession(request) != session) 119 { 120 // this session is not yet bound, bind it 121 bind(request, session); 122 } 123 else 124 { 125 setWicketSession(request, session); 126 } 127 } 128 129 @Override 130 public void destroy() 131 { 132 } 133 134 @Override 135 public String getSessionId(final Request request, final boolean create) 136 { 137 String id = null; 138 139 HttpSession httpSession = getHttpSession(request, false); 140 if (httpSession != null) 141 { 142 id = httpSession.getId(); 143 } 144 else if (create) 145 { 146 httpSession = getHttpSession(request, true); 147 id = httpSession.getId(); 148 149 IRequestLogger logger = Application.get().getRequestLogger(); 150 if (logger != null) 151 { 152 logger.sessionCreated(id); 153 } 154 } 155 return id; 156 } 157 158 @Override 159 public final void invalidate(final Request request) 160 { 161 HttpSession httpSession = getHttpSession(request, false); 162 if (httpSession != null) 163 { 164 // tell the app server the session is no longer valid 165 httpSession.invalidate(); 166 } 167 } 168 169 @Override 170 public final Session lookup(final Request request) 171 { 172 String sessionId = getSessionId(request, false); 173 if (sessionId != null) 174 { 175 return getWicketSession(request); 176 } 177 return null; 178 } 179 180 /** 181 * Reads the Wicket {@link Session} from the {@link HttpSession}'s attribute 182 * 183 * @param request The Wicket request 184 * @return The Wicket Session or {@code null} 185 */ 186 protected Session getWicketSession(final Request request) 187 { 188 return (Session) getAttribute(request, Session.SESSION_ATTRIBUTE_NAME); 189 } 190 191 /** 192 * Stores the Wicket {@link Session} in an attribute in the {@link HttpSession} 193 * 194 * @param request The Wicket request 195 * @param session The Wicket session 196 */ 197 protected void setWicketSession(final Request request, final Session session) 198 { 199 setAttribute(request, Session.SESSION_ATTRIBUTE_NAME, session); 200 } 201 202 /** 203 * Template method that is called when a session is being bound to the session store. It is 204 * called <strong>before</strong> the session object itself is added to this store (which is 205 * done by calling {@link ISessionStore#setAttribute(Request, String, Serializable)} with key 206 * {@link Session#SESSION_ATTRIBUTE_NAME}. 207 * 208 * @param request 209 * The request 210 * @param newSession 211 * The new session 212 */ 213 protected void onBind(final Request request, final Session newSession) 214 { 215 } 216 217 /** 218 * Template method that is called when the session is being detached from the store, which 219 * typically happens when the {@link HttpSession} was invalidated. 220 * 221 * @param sessionId 222 * The session id of the session that was invalidated. 223 */ 224 protected void onUnbind(final String sessionId) 225 { 226 } 227 228 /** 229 * Gets the prefix for storing variables in the actual session (typically {@link HttpSession}) 230 * for this application instance. 231 * 232 * @param request 233 * the request 234 * 235 * @return the prefix for storing variables in the actual session 236 */ 237 private String getSessionAttributePrefix(final Request request) 238 { 239 String sessionAttributePrefix = MarkupParser.WICKET; 240 241 if (request instanceof WebRequest) 242 { 243 sessionAttributePrefix = WebApplication.get().getSessionAttributePrefix( 244 (WebRequest)request, null); 245 } 246 247 return sessionAttributePrefix; 248 } 249 250 @Override 251 public final Serializable getAttribute(final Request request, final String name) 252 { 253 HttpSession httpSession = getHttpSession(request, false); 254 if (httpSession != null) 255 { 256 return (Serializable)httpSession.getAttribute(getSessionAttributePrefix(request) + name); 257 } 258 return null; 259 } 260 261 @Override 262 public final List<String> getAttributeNames(final Request request) 263 { 264 List<String> list = new ArrayList<String>(); 265 HttpSession httpSession = getHttpSession(request, false); 266 if (httpSession != null) 267 { 268 @SuppressWarnings("unchecked") 269 final Enumeration<String> names = httpSession.getAttributeNames(); 270 final String prefix = getSessionAttributePrefix(request); 271 while (names.hasMoreElements()) 272 { 273 final String name = names.nextElement(); 274 if (name.startsWith(prefix)) 275 { 276 list.add(name.substring(prefix.length())); 277 } 278 } 279 } 280 return list; 281 } 282 283 @Override 284 public final void removeAttribute(final Request request, final String name) 285 { 286 HttpSession httpSession = getHttpSession(request, false); 287 if (httpSession != null) 288 { 289 String attributeName = getSessionAttributePrefix(request) + name; 290 291 IRequestLogger logger = Application.get().getRequestLogger(); 292 if (logger != null) 293 { 294 Object value = httpSession.getAttribute(attributeName); 295 if (value != null) 296 { 297 logger.objectRemoved(value); 298 } 299 } 300 httpSession.removeAttribute(attributeName); 301 } 302 } 303 304 @Override 305 public final void setAttribute(final Request request, final String name, 306 final Serializable value) 307 { 308 // ignore call if the session was marked invalid 309 HttpSession httpSession = getHttpSession(request, false); 310 if (httpSession != null) 311 { 312 String attributeName = getSessionAttributePrefix(request) + name; 313 IRequestLogger logger = Application.get().getRequestLogger(); 314 if (logger != null) 315 { 316 if (httpSession.getAttribute(attributeName) == null) 317 { 318 logger.objectCreated(value); 319 } 320 else 321 { 322 logger.objectUpdated(value); 323 } 324 } 325 httpSession.setAttribute(attributeName, value); 326 } 327 } 328 329 @Override 330 public final void registerUnboundListener(final UnboundListener listener) 331 { 332 unboundListeners.add(listener); 333 } 334 335 @Override 336 public final void unregisterUnboundListener(final UnboundListener listener) 337 { 338 unboundListeners.remove(listener); 339 } 340 341 @Override 342 public final Set<UnboundListener> getUnboundListener() 343 { 344 return Collections.unmodifiableSet(unboundListeners); 345 } 346 347 /** 348 * Registers listener invoked when session is bound. 349 * 350 * @param listener 351 */ 352 @Override 353 public void registerBindListener(BindListener listener) 354 { 355 bindListeners.add(listener); 356 } 357 358 /** 359 * Unregisters listener invoked when session is bound. 360 * 361 * @param listener 362 */ 363 @Override 364 public void unregisterBindListener(BindListener listener) 365 { 366 bindListeners.remove(listener); 367 } 368 369 /** 370 * @return The list of registered bind listeners 371 */ 372 @Override 373 public Set<BindListener> getBindListeners() 374 { 375 return Collections.unmodifiableSet(bindListeners); 376 } 377 378 /** 379 * Reacts on unbinding from the session by cleaning up the session related data. 380 */ 381 protected static final class SessionBindingListener 382 implements 383 HttpSessionBindingListener, 384 Serializable 385 { 386 private static final long serialVersionUID = 1L; 387 388 /** The unique key of the application within this web application. */ 389 private final String applicationKey; 390 391 /** 392 * The Wicket Session associated with the expiring HttpSession 393 */ 394 private final Session wicketSession; 395 396 /** 397 * Construct. 398 * 399 * @param applicationKey 400 * The unique key of the application within this web application 401 * @param wicketSession 402 * The Wicket Session associated with the expiring http session 403 */ 404 public SessionBindingListener(final String applicationKey, final Session wicketSession) 405 { 406 this.applicationKey = applicationKey; 407 this.wicketSession = wicketSession; 408 } 409 410 @Override 411 public void valueBound(final HttpSessionBindingEvent evg) 412 { 413 } 414 415 @Override 416 public void valueUnbound(final HttpSessionBindingEvent evt) 417 { 418 String sessionId = evt.getSession().getId(); 419 420 log.debug("Session unbound: {}", sessionId); 421 422 if (wicketSession != null) 423 { 424 wicketSession.onInvalidate(); 425 } 426 427 Application application = Application.get(applicationKey); 428 if (application == null) 429 { 430 log.debug("Wicket application with name '{}' not found.", applicationKey); 431 return; 432 } 433 434 ISessionStore sessionStore = application.getSessionStore(); 435 if (sessionStore != null) 436 { 437 if (sessionStore instanceof HttpSessionStore) 438 { 439 ((HttpSessionStore) sessionStore).onUnbind(sessionId); 440 } 441 442 for (UnboundListener listener : sessionStore.getUnboundListener()) 443 { 444 listener.sessionUnbound(sessionId); 445 } 446 } 447 } 448 } 449}