0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.wicket;
0018:
0019: import java.io.Serializable;
0020: import java.util.ArrayList;
0021: import java.util.Collections;
0022: import java.util.HashMap;
0023: import java.util.HashSet;
0024: import java.util.Iterator;
0025: import java.util.LinkedList;
0026: import java.util.List;
0027: import java.util.Locale;
0028: import java.util.Map;
0029: import java.util.Set;
0030: import java.util.Map.Entry;
0031:
0032: import org.apache.wicket.application.IClassResolver;
0033: import org.apache.wicket.authorization.IAuthorizationStrategy;
0034: import org.apache.wicket.feedback.FeedbackMessage;
0035: import org.apache.wicket.feedback.FeedbackMessages;
0036: import org.apache.wicket.feedback.IFeedbackMessageFilter;
0037: import org.apache.wicket.protocol.http.IgnoreAjaxRequestException;
0038: import org.apache.wicket.request.ClientInfo;
0039: import org.apache.wicket.session.ISessionStore;
0040: import org.apache.wicket.settings.IRequestCycleSettings;
0041: import org.apache.wicket.util.lang.Objects;
0042: import org.apache.wicket.util.string.Strings;
0043: import org.apache.wicket.util.time.Duration;
0044: import org.slf4j.Logger;
0045: import org.slf4j.LoggerFactory;
0046:
0047: /**
0048: * Holds information about a user session, including some fixed number of most
0049: * recent pages (and all their nested component information).
0050: * <ul>
0051: * <li><b>Access via RequestCycle </b>- The Session for a {@link RequestCycle}
0052: * can be retrieved by calling {@link RequestCycle#getSession()}.
0053: *
0054: * <li><b>Access via Component </b>- If a RequestCycle object is not available,
0055: * the Session can be retrieved for a Component by calling
0056: * {@link Component#getSession()}. As currently implemented, each Component
0057: * does not itself have a reference to the session that contains it. However,
0058: * the Page component at the root of the containment hierarchy does have a
0059: * reference to the Session that holds the Page. So
0060: * {@link Component#getSession()} traverses the component hierarchy to the root
0061: * Page and then calls {@link Page#getSession()}.
0062: *
0063: * <li><b>Access via Thread Local </b>- In the odd case where neither a
0064: * RequestCycle nor a Component is available, the currently active Session for
0065: * the calling thread can be retrieved by calling the static method
0066: * Session.get(). This last form should only be used if the first two forms
0067: * cannot be used since thread local access can involve a potentially more
0068: * expensive hash map lookup.
0069: *
0070: * <li><b>Locale </b>- A session has a Locale property to support localization.
0071: * The Locale for a session can be set by calling
0072: * {@link Session#setLocale(Locale)}. The Locale for a Session determines how
0073: * localized resources are found and loaded.
0074: *
0075: * <li><b>Style </b>- Besides having an appearance based on locale, resources
0076: * can also have different looks in the same locale (a.k.a. "skins"). The style
0077: * for a session determines the look which is used within the appopriate locale.
0078: * The session style ("skin") can be set with the setStyle() method.
0079: *
0080: * <li><b>Resource Loading </b>- Based on the Session locale and style,
0081: * searching for resources occurs in the following order (where sourcePath is
0082: * set via the ApplicationSettings object for the current Application, and style
0083: * and locale are Session properties):
0084: * <ul>
0085: * 1. [sourcePath]/name[style][locale].[extension] <br>
0086: * 2. [sourcePath]/name[locale].[extension] <br>
0087: * 3. [sourcePath]/name[style].[extension] <br>
0088: * 4. [sourcePath]/name.[extension] <br>
0089: * 5. [classPath]/name[style][locale].[extension] <br>
0090: * 6. [classPath]/name[locale].[extension] <br>
0091: * 7. [classPath]/name[style].[extension] <br>
0092: * 8. [classPath]/name.[extension] <br>
0093: * </ul>
0094: *
0095: * <li><b>Session Properties </b>- Arbitrary objects can be attached to a
0096: * Session by installing a session factory on your Application class which
0097: * creates custom Session subclasses that have typesafe properties specific to
0098: * the application (see {@link Application} for details). To discourage
0099: * non-typesafe access to Session properties, no setProperty() or getProperty()
0100: * method is provided. In a clustered environment, you should take care to call
0101: * the dirty() method when you change a property or youre own. This way the
0102: * session will be reset again in the http session so that the http session
0103: * knows the session is changed.
0104: *
0105: * <li><b>Class Resolver </b>- Sessions have a class resolver (
0106: * {@link IClassResolver}) implementation that is used to locate classes for
0107: * components such as pages.
0108: *
0109: * <li><b>Page Factory </b>- A pluggable implementation of {@link IPageFactory}
0110: * is used to instantiate pages for the session.
0111: *
0112: * <li><b>Removal </b>- Pages can be removed from the Session forcibly by
0113: * calling remove(Page) or removeAll(), although such an action should rarely be
0114: * necessary.
0115: *
0116: * <li><b>Flash Messages</b>- Flash messages are messages that are stored in
0117: * session and are removed after they are displayed to the user. Session acts as
0118: * a store for these messages because they can last across requests.
0119: *
0120: * @author Jonathan Locke
0121: * @author Eelco Hillenius
0122: * @author Igor Vaynberg (ivaynberg)
0123: */
0124: public abstract class Session implements IClusterable {
0125: /**
0126: * Visitor interface for visiting page maps
0127: *
0128: * @author Jonathan Locke
0129: */
0130: public static interface IPageMapVisitor {
0131: /**
0132: * @param pageMap
0133: * The page map
0134: */
0135: public void pageMap(final IPageMap pageMap);
0136: }
0137:
0138: /**
0139: * meta data for recording map map access.
0140: */
0141: public static final class PageMapAccessMetaData implements
0142: IClusterable {
0143: private static final long serialVersionUID = 1L;
0144:
0145: Set pageMapNames = new HashSet(2);
0146:
0147: /**
0148: * @param pagemap
0149: * the pagemap to add as used.
0150: * @return the boolean if it was added (didn't already contain the
0151: * pagemap)
0152: */
0153: public boolean add(IPageMap pagemap) {
0154: return pageMapNames.add(pagemap.getName());
0155: }
0156: }
0157:
0158: /**
0159: * Filter that returns all component scoped messages ({@link FeedbackMessage#getReporter()} !=
0160: * null).
0161: */
0162: public static final IFeedbackMessageFilter MESSAGES_FOR_COMPONENTS = new IFeedbackMessageFilter() {
0163: private static final long serialVersionUID = 1L;
0164:
0165: public boolean accept(FeedbackMessage message) {
0166: return message.getReporter() != null;
0167: }
0168: };
0169:
0170: /** meta data key for missing body tags logging. */
0171: public static final MetaDataKey PAGEMAP_ACCESS_MDK = new MetaDataKey(
0172: PageMapAccessMetaData.class) {
0173: private static final long serialVersionUID = 1L;
0174: };
0175:
0176: /** Name of session attribute under which this session is stored */
0177: public static final String SESSION_ATTRIBUTE_NAME = "session";
0178:
0179: /** Thread-local current session. */
0180: private static final ThreadLocal current = new ThreadLocal();
0181:
0182: /** A store for dirty objects for one request */
0183: private static final ThreadLocal dirtyObjects = new ThreadLocal();
0184:
0185: /** Logging object */
0186: private static final Logger log = LoggerFactory
0187: .getLogger(Session.class);
0188:
0189: /** Attribute prefix for page maps stored in the session */
0190: private static final String pageMapAttributePrefix = "m:";
0191:
0192: /**
0193: * Filter that returns all session scoped messages ({@link FeedbackMessage#getReporter()} ==
0194: * null).
0195: */
0196: private static final IFeedbackMessageFilter RENDERED_SESSION_SCOPED_MESSAGES = new IFeedbackMessageFilter() {
0197: private static final long serialVersionUID = 1L;
0198:
0199: public boolean accept(FeedbackMessage message) {
0200: return message.getReporter() == null
0201: && message.isRendered();
0202: }
0203: };
0204:
0205: private static final long serialVersionUID = 1L;
0206:
0207: /** A store for touched pages for one request */
0208: private static final ThreadLocal touchedPages = new ThreadLocal();
0209:
0210: /** Prefix for attributes holding page map entries */
0211: static final String pageMapEntryAttributePrefix = "p:";
0212:
0213: /**
0214: * Checks if the <code>Session</code> threadlocal is set in this thread
0215: *
0216: * @return true if {@link Session#get()} can return the instance of session,
0217: * false otherwise
0218: */
0219: public static boolean exists() {
0220: return current.get() != null;
0221: }
0222:
0223: /**
0224: * Locate the session for the client of this request in the
0225: * {@link ISessionStore} or create a new one and attach it when none could
0226: * be located and sets it as the current instance for this thread.
0227: * Typically, clients never touch this method, but rather use
0228: * {@link Session#get()}, which does the locating implicitly when not yet
0229: * set as a thread local.
0230: *
0231: * @return The session for the client of this request or a new, unbound
0232: */
0233: public static final Session findOrCreate() {
0234: RequestCycle requestCycle = RequestCycle.get();
0235: if (requestCycle == null) {
0236: throw new IllegalStateException(
0237: "you can only locate or create sessions in the context of a request cycle");
0238: }
0239: Application application = Application.get();
0240: ISessionStore sessionStore = application.getSessionStore();
0241: Session session = sessionStore
0242: .lookup(requestCycle.getRequest());
0243:
0244: if (session == null) {
0245: // Create session using session factory
0246: session = application.newSession(requestCycle.getRequest(),
0247: requestCycle.getResponse());
0248: }
0249:
0250: // set thread local
0251: set(session);
0252:
0253: return session;
0254: }
0255:
0256: /**
0257: * Get the session for the calling thread.
0258: *
0259: * @return Session for calling thread
0260: */
0261: public static Session get() {
0262: Session session = (Session) current.get();
0263: if (session == null) {
0264: session = findOrCreate();
0265: }
0266: return session;
0267: }
0268:
0269: /**
0270: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0271: * <p>
0272: * Sets session for calling thread. Also triggers {@link #attach()} being
0273: * called.
0274: *
0275: * @param session
0276: * The session
0277: */
0278: public static void set(final Session session) {
0279: if (session == null) {
0280: throw new IllegalArgumentException(
0281: "Argument session can not be null");
0282: }
0283:
0284: current.set(session);
0285:
0286: // execute any attach logic now
0287: session.attach();
0288: }
0289:
0290: /**
0291: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0292: * <p>
0293: * Clears the session for calling thread.
0294: *
0295: */
0296: public static void unset() {
0297: current.set(null);
0298: }
0299:
0300: /** A number to generate names for auto create pagemaps */
0301: private int autoCreatePageMapCounter = 0;
0302:
0303: /**
0304: * Cached instance of agent info which is typically designated by calling
0305: * {@link RequestCycle#newClientInfo()}.
0306: */
0307: private ClientInfo clientInfo;
0308:
0309: /** True if session state has been changed */
0310: private transient boolean dirty = false;
0311:
0312: /** feedback messages */
0313: private final FeedbackMessages feedbackMessages = new FeedbackMessages();
0314:
0315: /** cached id because you can't access the id after session unbound */
0316: private String id = null;
0317:
0318: /** The locale to use when loading resources for this session. */
0319: private Locale locale;
0320:
0321: /** Application level meta data. */
0322: private MetaDataEntry[] metaData;
0323:
0324: /**
0325: * We need to know both thread that keeps the pagemap lock and the
0326: * RequestCycle
0327: */
0328: private static class PageMapsUsedInRequestEntry {
0329: Thread thread;
0330: RequestCycle requestCycle;
0331: };
0332:
0333: private transient Map pageMapsUsedInRequest;
0334:
0335: /** True, if session has been invalidated */
0336: private transient boolean sessionInvalidated = false;
0337:
0338: /**
0339: * Temporary instance of the session store. Should be set on each request as
0340: * it is not supposed to go in the session.
0341: */
0342: private transient ISessionStore sessionStore;
0343:
0344: /** Any special "skin" style to use when loading resources. */
0345: private String style;
0346:
0347: /**
0348: * Holds attributes for sessions that are still temporary/ not bound to a
0349: * session store. Only used when {@link #isTemporary()} is true.
0350: * <p>
0351: * Note: this doesn't have to be synchronized, as the only time when this
0352: * map is used is when a session is temporary, in which case it won't be
0353: * shared between requests (it's a per request instance).
0354: * </p>
0355: */
0356: private transient Map temporarySessionAttributes;
0357:
0358: /** A linked list for last used pagemap queue */
0359: private final LinkedList/* <IPageMap> */usedPageMaps = new LinkedList();
0360:
0361: /**
0362: * Constructor. Note that {@link RequestCycle} is not available until this
0363: * constructor returns.
0364: *
0365: * @param request
0366: * The current request
0367: */
0368: public Session(Request request) {
0369: locale = request.getLocale();
0370: if (locale == null) {
0371: throw new IllegalArgumentException(
0372: "Parameter 'locale' must not be null");
0373: }
0374: }
0375:
0376: /**
0377: * Constructor. Note that {@link RequestCycle} is not available until this
0378: * constructor returns.
0379: *
0380: * @deprecated Use #Session(Request)
0381: *
0382: * @param application
0383: * The application that this is a session of
0384: * @param request
0385: * The current request
0386: */
0387: protected Session(Application application, Request request) {
0388: this (request);
0389: }
0390:
0391: /**
0392: * Force binding this session to the application's
0393: * {@link ISessionStore session store} if not already done so.
0394: * <p>
0395: * A Wicket application can operate in a session-less mode as long as
0396: * stateless pages are used. Session objects will be then created for each
0397: * request, but they will only live for that request. You can recognize
0398: * temporary sessions by calling {@link #isTemporary()} which basically
0399: * checks whether the session's id is null. Hence, temporary sessions have
0400: * no session id.
0401: * </p>
0402: * <p>
0403: * By calling this method, the session will be bound (made not-temporary) if
0404: * it was not bound yet. It is useful for cases where you want to be
0405: * absolutely sure this session object will be available in next requests.
0406: * If the session was already bound ({@link ISessionStore#lookup(Request) returns a session}),
0407: * this call will be a noop.
0408: * </p>
0409: */
0410: public final void bind() {
0411: ISessionStore store = getSessionStore();
0412: Request request = RequestCycle.get().getRequest();
0413: if (store.lookup(request) == null) {
0414: // explicitly create a session
0415: id = store.getSessionId(request, true);
0416: // bind it
0417: store.bind(request, this );
0418:
0419: if (temporarySessionAttributes != null) {
0420: for (Iterator i = temporarySessionAttributes.entrySet()
0421: .iterator(); i.hasNext();) {
0422: Entry entry = (Entry) i.next();
0423: store.setAttribute(request, String.valueOf(entry
0424: .getKey()), entry.getValue());
0425: }
0426: temporarySessionAttributes = null;
0427: }
0428: }
0429: }
0430:
0431: /**
0432: * Cleans up all rendered feedback messages and any unrendered, dangling
0433: * feedback messages there may be left after that.
0434: */
0435: public void cleanupFeedbackMessages() {
0436: // remove all component feedback messages if we are either using one
0437: // pass or render to buffer render strategy (in which case we can remove
0438: // without further delay) or in case the redirect to render strategy is
0439: // used, when we're doing the render request (isRedirect should return
0440: // false in that case)
0441: if (Application.get().getRequestCycleSettings()
0442: .getRenderStrategy() != IRequestCycleSettings.REDIRECT_TO_RENDER
0443: || (!RequestCycle.get().isRedirect())) {
0444: // If session scoped, rendered messages got indeed cleaned up, mark
0445: // the session as dirty
0446: if (feedbackMessages
0447: .clear(RENDERED_SESSION_SCOPED_MESSAGES) > 0) {
0448: dirty();
0449: }
0450:
0451: // clean up all component related feedback messages
0452: feedbackMessages.clear(MESSAGES_FOR_COMPONENTS);
0453: }
0454: }
0455:
0456: /**
0457: * Removes all pages from the session. Although this method should rarely be
0458: * needed, it is available (possibly for security reasons).
0459: */
0460: public final void clear() {
0461: visitPageMaps(new IPageMapVisitor() {
0462: public void pageMap(IPageMap pageMap) {
0463: pageMap.clear();
0464: }
0465: });
0466: }
0467:
0468: /**
0469: * Automatically creates a page map, giving it a session unique name.
0470: *
0471: * @return Created PageMap
0472: */
0473: public final IPageMap createAutoPageMap() {
0474: return newPageMap(createAutoPageMapName());
0475: }
0476:
0477: protected int currentCreateAutoPageMapCounter() {
0478: return autoCreatePageMapCounter;
0479: }
0480:
0481: protected void incrementCreateAutoPageMapCounter() {
0482: ++autoCreatePageMapCounter;
0483: }
0484:
0485: /**
0486: * With this call you can create a pagemap name but not create the pagemap
0487: * itself already. It will give the first pagemap name where it couldn't
0488: * find a current pagemap for.
0489: *
0490: * It will return the same name if you call it 2 times in a row.
0491: *
0492: * @return The created pagemap name
0493: */
0494: public synchronized final String createAutoPageMapName() {
0495: String name = getAutoPageMapNamePrefix()
0496: + currentCreateAutoPageMapCounter()
0497: + getAutoPageMapNameSuffix();
0498: IPageMap pm = pageMapForName(name, false);
0499: while (pm != null) {
0500: incrementCreateAutoPageMapCounter();
0501: name = getAutoPageMapNamePrefix()
0502: + currentCreateAutoPageMapCounter()
0503: + getAutoPageMapNameSuffix();
0504: pm = pageMapForName(name, false);
0505: }
0506: return name;
0507: }
0508:
0509: /**
0510: * @return
0511: */
0512: protected String getAutoPageMapNamePrefix() {
0513: return "wicket-";
0514: }
0515:
0516: /**
0517: * @return
0518: */
0519: protected String getAutoPageMapNameSuffix() {
0520: return "";
0521: }
0522:
0523: /**
0524: * Registers an error feedback message for this session
0525: *
0526: * @param message
0527: * The feedback message
0528: */
0529: public final void error(final String message) {
0530: addFeedbackMessage(message, FeedbackMessage.ERROR);
0531: }
0532:
0533: /**
0534: * Get the application that is currently working with this session.
0535: *
0536: * @return Returns the application.
0537: */
0538: public final Application getApplication() {
0539: return Application.get();
0540: }
0541:
0542: /**
0543: * @return The authorization strategy for this session
0544: */
0545: public IAuthorizationStrategy getAuthorizationStrategy() {
0546: return getApplication().getSecuritySettings()
0547: .getAuthorizationStrategy();
0548: }
0549:
0550: /**
0551: * @return The class resolver for this Session
0552: */
0553: public final IClassResolver getClassResolver() {
0554: return getApplication().getApplicationSettings()
0555: .getClassResolver();
0556: }
0557:
0558: /**
0559: * Gets the client info object for this session. This method lazily gets the
0560: * new agent info object for this session. It uses any cached or set ({@link #setClientInfo(ClientInfo)})
0561: * client info object or uses {@link RequestCycle#newClientInfo()} to get
0562: * the info object based on the current request when no client info object
0563: * was set yet, and then caches the returned object; we can expect the
0564: * client to stay the same for the whole session, and implementations of
0565: * {@link RequestCycle#newClientInfo()} might be relatively expensive.
0566: *
0567: * @return the client info object based on this request
0568: */
0569: public ClientInfo getClientInfo() {
0570: if (clientInfo == null) {
0571: clientInfo = RequestCycle.get().newClientInfo();
0572: }
0573: return clientInfo;
0574: }
0575:
0576: /**
0577: * @return The default page map
0578: */
0579: public final IPageMap getDefaultPageMap() {
0580: return pageMapForName(PageMap.DEFAULT_NAME, true);
0581: }
0582:
0583: /**
0584: * Gets feedback messages stored in session
0585: *
0586: * @return unmodifiable list of feedback messages
0587: */
0588: public final FeedbackMessages getFeedbackMessages() {
0589: return feedbackMessages;
0590: }
0591:
0592: /**
0593: * Gets the unique id for this session from the underlying SessionStore. May
0594: * be null if a concrete session is not yet created.
0595: *
0596: * @return The unique id for this session or null if it is a temporary
0597: * session
0598: */
0599: public final String getId() {
0600: if (id == null) {
0601: id = getSessionStore().getSessionId(
0602: RequestCycle.get().getRequest(), false);
0603:
0604: // we have one?
0605: if (id != null) {
0606: dirty();
0607: }
0608: }
0609: return id;
0610: }
0611:
0612: /**
0613: * Get this session's locale.
0614: *
0615: * @return This session's locale
0616: */
0617: public Locale getLocale() {
0618: return locale;
0619: }
0620:
0621: /**
0622: * Gets metadata for this session using the given key.
0623: *
0624: * @param key
0625: * The key for the data
0626: * @return The metadata
0627: * @see MetaDataKey
0628: */
0629: public final Serializable getMetaData(final MetaDataKey key) {
0630: return key.get(metaData);
0631: }
0632:
0633: /**
0634: * When a regular request on certain page with certain version is being
0635: * processed, we don't allow ajax requests to same page and version.
0636: *
0637: * @param lockedRequestCycle
0638: * @return whether current request is valid or sould be discarded
0639: */
0640: protected boolean isCurrentRequestValid(
0641: RequestCycle lockedRequestCycle) {
0642: return true;
0643: }
0644:
0645: /**
0646: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0647: *
0648: * Returns the page with given id and versionNumber. It keeps asking
0649: * pageMaps for given page until it finds one that contains it.
0650: *
0651: * @param pageId
0652: * @param versionNumber
0653: * @return
0654: */
0655: public final Page getPage(final int pageId, final int versionNumber) {
0656: if (Application.get().getSessionSettings()
0657: .isPageIdUniquePerSession() == false) {
0658: throw new IllegalStateException(
0659: "To call this method ISessionSettings.setPageIdUniquePerSession must be set to true");
0660: }
0661:
0662: List pageMaps = getPageMaps();
0663:
0664: for (Iterator i = pageMaps.iterator(); i.hasNext();) {
0665: IPageMap pm = (IPageMap) i.next();
0666: if (pm.containsPage(pageId, versionNumber)) {
0667: return getPage(pm.getName(), "" + pageId, versionNumber);
0668: }
0669: }
0670:
0671: return null;
0672: }
0673:
0674: /**
0675: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0676: *
0677: * Get the page for the given path.
0678: *
0679: * @param pageMapName
0680: * The name of the page map where the page is
0681: * @param path
0682: * Component path
0683: * @param versionNumber
0684: * The version of the page required
0685: * @return The page based on the first path component (the page id), or null
0686: * if the requested version of the page cannot be found.
0687: */
0688: public final Page getPage(final String pageMapName,
0689: final String path, final int versionNumber) {
0690: if (log.isDebugEnabled()) {
0691: log.debug("Getting page [path = " + path
0692: + ", versionNumber = " + versionNumber + "]");
0693: }
0694:
0695: // Get page map by name, creating the default page map automatically
0696: IPageMap pageMap = pageMapForName(pageMapName,
0697: pageMapName == PageMap.DEFAULT_NAME);
0698: if (pageMap != null) {
0699: synchronized (usedPageMaps) // get a lock so be sure that only one
0700: // is made
0701: {
0702: if (pageMapsUsedInRequest == null) {
0703: pageMapsUsedInRequest = new HashMap(3);
0704: }
0705: }
0706: synchronized (pageMapsUsedInRequest) {
0707: long startTime = System.currentTimeMillis();
0708:
0709: // TODO For now only use the setting. Might be extended with
0710: // something overridable on request/ page/ request target level
0711: // later
0712: Duration timeout = Application.get()
0713: .getRequestCycleSettings().getTimeout();
0714:
0715: PageMapsUsedInRequestEntry entry = (PageMapsUsedInRequestEntry) pageMapsUsedInRequest
0716: .get(pageMap);
0717:
0718: // Get page entry for id and version
0719: Thread t = entry != null ? entry.thread : null;
0720: while (t != null && t != Thread.currentThread()) {
0721: if (isCurrentRequestValid(entry.requestCycle) == false) {
0722: // we need to ignore this request. That's because it is
0723: // an ajax request
0724: // while regular page request is being processed
0725: throw new IgnoreAjaxRequestException();
0726: }
0727:
0728: try {
0729: pageMapsUsedInRequest.wait(timeout
0730: .getMilliseconds());
0731: } catch (InterruptedException ex) {
0732: throw new WicketRuntimeException(ex);
0733: }
0734:
0735: entry = (PageMapsUsedInRequestEntry) pageMapsUsedInRequest
0736: .get(pageMap);
0737: t = entry != null ? entry.thread : null;
0738:
0739: if (t != null
0740: && t != Thread.currentThread()
0741: && (startTime + timeout.getMilliseconds()) < System
0742: .currentTimeMillis()) {
0743: // if it is still not the right thread..
0744: // This either points to long running code (a report
0745: // page?) or a deadlock or such
0746: throw new WicketRuntimeException(
0747: "After "
0748: + timeout
0749: + " the Pagemap "
0750: + pageMapName
0751: + " is still locked by: "
0752: + t
0753: + ", giving up trying to get the page for path: "
0754: + path);
0755: }
0756: }
0757:
0758: PageMapsUsedInRequestEntry newEntry = new PageMapsUsedInRequestEntry();
0759: newEntry.thread = Thread.currentThread();
0760: newEntry.requestCycle = RequestCycle.get();
0761: pageMapsUsedInRequest.put(pageMap, newEntry);
0762: final String id = Strings.firstPathComponent(path,
0763: Component.PATH_SEPARATOR);
0764: Page page = pageMap.get(Integer.parseInt(id),
0765: versionNumber);
0766: if (page == null) {
0767: pageMapsUsedInRequest.remove(pageMap);
0768: pageMapsUsedInRequest.notifyAll();
0769: } else {
0770: // attach the page now.
0771: page.attach();
0772: touch(page);
0773: }
0774: return page;
0775: }
0776: }
0777: return null;
0778: }
0779:
0780: /**
0781: * @return The page factory for this session
0782: */
0783: public final IPageFactory getPageFactory() {
0784: return getApplication().getSessionSettings().getPageFactory();
0785: }
0786:
0787: /**
0788: * @param page
0789: * The page, or null if no page context is available
0790: * @return The page factory for the page, or the default page factory if
0791: * page was null
0792: */
0793: public final IPageFactory getPageFactory(final Page page) {
0794: if (page != null) {
0795: return page.getPageFactory();
0796: }
0797: return getPageFactory();
0798: }
0799:
0800: /**
0801: * @return A list of all PageMaps in this session.
0802: */
0803: public final List getPageMaps() {
0804: final List list = new ArrayList();
0805: for (final Iterator iterator = getAttributeNames().iterator(); iterator
0806: .hasNext();) {
0807: final String attribute = (String) iterator.next();
0808: if (attribute.startsWith(pageMapAttributePrefix)) {
0809: list.add(getAttribute(attribute));
0810: }
0811: }
0812: return list;
0813: }
0814:
0815: /**
0816: * @return Size of this session, including all the pagemaps it contains
0817: */
0818: public final long getSizeInBytes() {
0819: long size = Objects.sizeof(this );
0820: for (final Iterator iterator = getPageMaps().iterator(); iterator
0821: .hasNext();) {
0822: final IPageMap pageMap = (IPageMap) iterator.next();
0823: size += pageMap.getSizeInBytes();
0824: }
0825: return size;
0826: }
0827:
0828: /**
0829: * Get the style (see {@link org.apache.wicket.Session}).
0830: *
0831: * @return Returns the style (see {@link org.apache.wicket.Session})
0832: */
0833: public final String getStyle() {
0834: return style;
0835: }
0836:
0837: /**
0838: * Registers an informational feedback message for this session
0839: *
0840: * @param message
0841: * The feedback message
0842: */
0843: public final void info(final String message) {
0844: addFeedbackMessage(message, FeedbackMessage.INFO);
0845: }
0846:
0847: /**
0848: * Invalidates this session at the end of the current request. If you need
0849: * to invalidate the session immediately, you can do this by calling
0850: * invalidateNow(), however this will remove all Wicket components from this
0851: * session, which means that you will no longer be able to work with them.
0852: */
0853: public void invalidate() {
0854: sessionInvalidated = true;
0855: }
0856:
0857: /**
0858: * Invalidates this session immediately. Calling this method will remove all
0859: * Wicket components from this session, which means that you will no longer
0860: * be able to work with them.
0861: */
0862: public void invalidateNow() {
0863: sessionInvalidated = true; // set this for isSessionInvalidated
0864: getSessionStore().invalidate(RequestCycle.get().getRequest());
0865: }
0866:
0867: /**
0868: * Whether the session is invalid now, or will be invalidated by the end of
0869: * the request. Clients should rarely need to use this method if ever.
0870: *
0871: * @return Whether the session is invalid when the current request is done
0872: *
0873: * @see #invalidate()
0874: * @see #invalidateNow()
0875: */
0876: public final boolean isSessionInvalidated() {
0877: return sessionInvalidated;
0878: }
0879:
0880: /**
0881: * Whether this session is temporary. A Wicket application can operate in a
0882: * session-less mode as long as stateless pages are used. If this session
0883: * object is temporary, it will not be available on a next request.
0884: *
0885: * @return Whether this session is temporary (which is the same as it's id
0886: * being null)
0887: */
0888: public final boolean isTemporary() {
0889: return getId() == null;
0890: }
0891:
0892: /**
0893: * Creates a new page map with a given name
0894: *
0895: * @param name
0896: * The name for the new page map
0897: * @return The newly created page map
0898: */
0899: public final IPageMap newPageMap(final String name) {
0900: // Check that session doesn't have too many page maps already
0901: final int maxPageMaps = getApplication().getSessionSettings()
0902: .getMaxPageMaps();
0903: synchronized (usedPageMaps) {
0904: if (usedPageMaps.size() >= maxPageMaps) {
0905: IPageMap pm = (IPageMap) usedPageMaps.getFirst();
0906: pm.remove();
0907: }
0908: }
0909:
0910: // Create new page map
0911: final IPageMap pageMap = getSessionStore().createPageMap(name);
0912: setAttribute(attributeForPageMapName(name), pageMap);
0913: dirty();
0914: return pageMap;
0915: }
0916:
0917: /**
0918: * Gets a page map for the given name, automatically creating it if need be.
0919: *
0920: * @param pageMapName
0921: * Name of page map, or null for default page map
0922: * @param autoCreate
0923: * True if the page map should be automatically created if it
0924: * does not exist
0925: * @return PageMap for name
0926: */
0927: public final IPageMap pageMapForName(String pageMapName,
0928: final boolean autoCreate) {
0929: IPageMap pageMap = (IPageMap) getAttribute(attributeForPageMapName(pageMapName));
0930: if (pageMap == null && autoCreate) {
0931: pageMap = newPageMap(pageMapName);
0932: }
0933: return pageMap;
0934: }
0935:
0936: /**
0937: * @param pageMap
0938: * Page map to remove
0939: */
0940: public final void removePageMap(final IPageMap pageMap) {
0941: PageMapAccessMetaData pagemapMetaData = (PageMapAccessMetaData) getMetaData(PAGEMAP_ACCESS_MDK);
0942: if (pagemapMetaData != null) {
0943: pagemapMetaData.pageMapNames.remove(pageMap.getName());
0944: }
0945:
0946: synchronized (usedPageMaps) {
0947: usedPageMaps.remove(pageMap);
0948: }
0949:
0950: removeAttribute(attributeForPageMapName(pageMap.getName()));
0951: dirty();
0952: }
0953:
0954: /**
0955: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0956: * <p>
0957: * Sets the application that this session is associated with.
0958: *
0959: * @param application
0960: * The application
0961: */
0962: public final void setApplication(final Application application) {
0963: }
0964:
0965: /**
0966: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0967: * <p>
0968: * Sets the client info object for this session. This will only work when
0969: * {@link #getClientInfo()} is not overriden.
0970: *
0971: * @param clientInfo
0972: * the client info object
0973: */
0974: public final void setClientInfo(ClientInfo clientInfo) {
0975: this .clientInfo = clientInfo;
0976: dirty();
0977: }
0978:
0979: /**
0980: * Set the locale for this session.
0981: *
0982: * @param locale
0983: * New locale
0984: */
0985: public final void setLocale(final Locale locale) {
0986: if (locale == null) {
0987: throw new IllegalArgumentException(
0988: "Parameter 'locale' must not be null");
0989: }
0990: this .locale = locale;
0991: dirty();
0992: }
0993:
0994: /**
0995: * Sets the metadata for this session using the given key. If the metadata
0996: * object is not of the correct type for the metadata key, an
0997: * IllegalArgumentException will be thrown. For information on creating
0998: * MetaDataKeys, see {@link MetaDataKey}.
0999: *
1000: * @param key
1001: * The singleton key for the metadata
1002: * @param object
1003: * The metadata object
1004: * @throws IllegalArgumentException
1005: * @see MetaDataKey
1006: */
1007: public final void setMetaData(final MetaDataKey key,
1008: final Serializable object) {
1009: metaData = key.set(metaData, object);
1010: }
1011:
1012: /**
1013: * Set the style (see {@link org.apache.wicket.Session}).
1014: *
1015: * @param style
1016: * The style to set.
1017: * @return the Session object
1018: */
1019: public final Session setStyle(final String style) {
1020: this .style = style;
1021: dirty();
1022: return this ;
1023: }
1024:
1025: /**
1026: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
1027: * <p>
1028: * The page will be 'touched' in the session. If it wasn't added yet to the
1029: * pagemap, it will be added to the page map else it will set this page to
1030: * the front.
1031: *
1032: * If another page was removed because of this it will be cleaned up.
1033: *
1034: * @param page
1035: */
1036: public final void touch(Page page) {
1037: // store it in a list, so that the pages are really pushed
1038: // to the pagemap when the session does it update/detaches.
1039: // all the pages are then detached
1040: List lst = (List) touchedPages.get();
1041: if (lst == null) {
1042: lst = new ArrayList();
1043: touchedPages.set(lst);
1044: lst.add(page);
1045: } else if (!lst.contains(page)) {
1046: lst.add(page);
1047: }
1048: }
1049:
1050: /**
1051: * @param visitor
1052: * The visitor to call at each Page in this PageMap.
1053: */
1054: public final void visitPageMaps(final IPageMapVisitor visitor) {
1055: for (final Iterator iterator = getAttributeNames().iterator(); iterator
1056: .hasNext();) {
1057: final String attribute = (String) iterator.next();
1058: if (attribute.startsWith(pageMapAttributePrefix)) {
1059: visitor.pageMap((IPageMap) getAttribute(attribute));
1060: }
1061: }
1062: }
1063:
1064: /**
1065: * Registers a warning feedback message for this session
1066: *
1067: * @param message
1068: * The feedback message
1069: */
1070: public final void warn(final String message) {
1071: addFeedbackMessage(message, FeedbackMessage.WARNING);
1072: }
1073:
1074: /**
1075: * Adds a feedback message to the list of messages
1076: *
1077: * @param message
1078: * @param level
1079: *
1080: */
1081: private void addFeedbackMessage(String message, int level) {
1082: getFeedbackMessages().add(null, message, level);
1083: dirty();
1084: }
1085:
1086: /**
1087: * @param pageMapName
1088: * Name of page map
1089: * @return Session attribute holding page map
1090: */
1091: private final String attributeForPageMapName(
1092: final String pageMapName) {
1093: return pageMapAttributePrefix + pageMapName;
1094: }
1095:
1096: /**
1097: * Any attach logic for session subclasses. Called when a session is set for
1098: * the thread.
1099: */
1100: protected void attach() {
1101: }
1102:
1103: /**
1104: * Any detach logic for session subclasses. This is called on the end of
1105: * handling a request, when the RequestCycle is about to be detached from
1106: * the current thread.
1107: */
1108: protected void detach() {
1109: if (sessionInvalidated) {
1110: invalidateNow();
1111: }
1112: }
1113:
1114: /**
1115: * Marks session state as dirty so that it will be flushed at the end of the
1116: * request.
1117: */
1118: public final void dirty() {
1119: dirty = true;
1120: }
1121:
1122: /**
1123: * Gets the attribute value with the given name
1124: *
1125: * @param name
1126: * The name of the attribute to store
1127: * @return The value of the attribute
1128: */
1129: protected final Object getAttribute(final String name) {
1130: if (!isTemporary()) {
1131: RequestCycle cycle = RequestCycle.get();
1132: if (cycle != null) {
1133: return getSessionStore().getAttribute(
1134: cycle.getRequest(), name);
1135: }
1136: } else {
1137: if (temporarySessionAttributes != null) {
1138: return temporarySessionAttributes.get(name);
1139: }
1140: }
1141: return null;
1142: }
1143:
1144: /**
1145: * @return List of attributes for this session
1146: */
1147: protected final List getAttributeNames() {
1148: if (!isTemporary()) {
1149: RequestCycle cycle = RequestCycle.get();
1150: if (cycle != null) {
1151: return getSessionStore().getAttributeNames(
1152: cycle.getRequest());
1153: }
1154: } else {
1155: if (temporarySessionAttributes != null) {
1156: return new ArrayList(temporarySessionAttributes
1157: .keySet());
1158: }
1159: }
1160: return Collections.EMPTY_LIST;
1161: }
1162:
1163: /**
1164: * Gets the session store.
1165: *
1166: * @return the session store
1167: */
1168: protected ISessionStore getSessionStore() {
1169: if (sessionStore == null) {
1170: sessionStore = getApplication().getSessionStore();
1171: }
1172: return sessionStore;
1173: }
1174:
1175: /**
1176: * Removes the attribute with the given name.
1177: *
1178: * @param name
1179: * the name of the attribute to remove
1180: */
1181: protected final void removeAttribute(String name) {
1182: if (!isTemporary()) {
1183: RequestCycle cycle = RequestCycle.get();
1184: if (cycle != null) {
1185: getSessionStore().removeAttribute(cycle.getRequest(),
1186: name);
1187: }
1188: } else {
1189: if (temporarySessionAttributes != null) {
1190: temporarySessionAttributes.remove(name);
1191: }
1192: }
1193: }
1194:
1195: /**
1196: * Adds or replaces the attribute with the given name and value.
1197: *
1198: * @param name
1199: * The name of the attribute
1200: * @param value
1201: * The value of the attribute
1202: */
1203: protected final void setAttribute(String name, Object value) {
1204: if (!isTemporary()) {
1205: RequestCycle cycle = RequestCycle.get();
1206: if (cycle == null) {
1207: throw new IllegalStateException(
1208: "Cannot set the attribute: no RequestCycle available");
1209: }
1210:
1211: ISessionStore store = getSessionStore();
1212: Request request = cycle.getRequest();
1213:
1214: // extra check on session binding event
1215: if (value == this ) {
1216: Object current = store.getAttribute(request, name);
1217: if (current == null) {
1218: String id = store.getSessionId(request, false);
1219: if (id != null) {
1220: // this is a new instance. wherever it came from, bind
1221: // the session now
1222: store.bind(request, (Session) value);
1223: }
1224: }
1225: }
1226:
1227: // Set the actual attribute
1228: store.setAttribute(request, name, value);
1229: } else {
1230: // we don't have to synchronize, as it is impossible a temporary
1231: // session instance gets shared across threads
1232: if (temporarySessionAttributes == null) {
1233: temporarySessionAttributes = new HashMap(3);
1234: }
1235: temporarySessionAttributes.put(name, value);
1236: }
1237: }
1238:
1239: /**
1240: * NOT TO BE CALLED BY FRAMEWORK USERS.
1241: *
1242: * @deprecated obsolete method (was meant for internal book keeping really).
1243: * Clients should override {@link #detach()} instead.
1244: */
1245: protected final void update() {
1246: throw new UnsupportedOperationException();
1247: }
1248:
1249: /**
1250: * @param page
1251: * The page to add to dirty objects list
1252: */
1253: void dirtyPage(final Page page) {
1254: List dirtyObjects = getDirtyObjectsList();
1255: if (!dirtyObjects.contains(page)) {
1256: dirtyObjects.add(page);
1257: }
1258: }
1259:
1260: /**
1261: * @param map
1262: * The page map to add to dirty objects list
1263: */
1264: void dirtyPageMap(final IPageMap map) {
1265: if (!map.isDefault()) {
1266: synchronized (usedPageMaps) {
1267: usedPageMaps.remove(map);
1268: usedPageMaps.addLast(map);
1269: }
1270: }
1271: List dirtyObjects = getDirtyObjectsList();
1272: if (!dirtyObjects.contains(map)) {
1273: dirtyObjects.add(map);
1274: }
1275: }
1276:
1277: /**
1278: * @return The current thread dirty objects list
1279: */
1280: List getDirtyObjectsList() {
1281: List list = (List) dirtyObjects.get();
1282: if (list == null) {
1283: list = new ArrayList(4);
1284: dirtyObjects.set(list);
1285: }
1286: return list;
1287: }
1288:
1289: // TODO remove after deprecation release
1290:
1291: /**
1292: * INTERNAL API. The request cycle when detached will call this.
1293: *
1294: */
1295: final void requestDetached() {
1296: List touchedPages = (List) Session.touchedPages.get();
1297: Session.touchedPages.set(null);
1298: if (touchedPages != null) {
1299: for (int i = 0; i < touchedPages.size(); i++) {
1300: Page page = (Page) touchedPages.get(i);
1301: page.getPageMap().put(page);
1302: }
1303: }
1304:
1305: // If state is dirty
1306: if (dirty) {
1307: // State is no longer dirty
1308: dirty = false;
1309:
1310: // Set attribute.
1311: setAttribute(SESSION_ATTRIBUTE_NAME, this );
1312: } else {
1313: if (log.isDebugEnabled()) {
1314: log.debug("update: Session not dirty.");
1315: }
1316: }
1317:
1318: List dirtyObjects = (List) Session.dirtyObjects.get();
1319: Session.dirtyObjects.set(null);
1320:
1321: Map tempMap = new HashMap();
1322:
1323: // Go through all dirty entries, replicating any dirty objects
1324: if (dirtyObjects != null) {
1325: for (final Iterator iterator = dirtyObjects.iterator(); iterator
1326: .hasNext();) {
1327: String attribute = null;
1328: Object object = iterator.next();
1329: if (object instanceof Page) {
1330: final Page page = (Page) object;
1331: if (page.isStateless()) {
1332: // check, can it be that stateless pages where added to
1333: // the session?
1334: // and should be removed now?
1335: continue;
1336: }
1337: attribute = page.getPageMap().attributeForId(
1338: page.getNumericId());
1339: if (getAttribute(attribute) == null) {
1340: // page removed by another thread. don't add it again.
1341: continue;
1342: }
1343: object = page.getPageMapEntry();
1344: } else if (object instanceof IPageMap) {
1345: attribute = attributeForPageMapName(((IPageMap) object)
1346: .getName());
1347: }
1348:
1349: // we might override some attributes, so we use a temporary map
1350: // and then just copy the last values to real sesssion
1351: tempMap.put(attribute, object);
1352: }
1353: }
1354:
1355: // in case we have dirty attributes, set them to session
1356: if (tempMap.isEmpty() == false) {
1357: for (Iterator i = tempMap.entrySet().iterator(); i
1358: .hasNext();) {
1359: Map.Entry entry = (Map.Entry) i.next();
1360: setAttribute((String) entry.getKey(), entry.getValue());
1361: }
1362: }
1363:
1364: if (pageMapsUsedInRequest != null) {
1365: synchronized (pageMapsUsedInRequest) {
1366: Thread t = Thread.currentThread();
1367: Iterator it = pageMapsUsedInRequest.entrySet()
1368: .iterator();
1369: while (it.hasNext()) {
1370: Entry entry = (Entry) it.next();
1371: if (((PageMapsUsedInRequestEntry) entry.getValue()).thread == t) {
1372: it.remove();
1373: }
1374: }
1375: pageMapsUsedInRequest.notifyAll();
1376: }
1377: }
1378: }
1379:
1380: private int pageIdCounter = 0;
1381:
1382: synchronized protected int nextPageId() {
1383: return pageIdCounter++;
1384: }
1385: }
|