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