0001: /*
0002: * $Id: Session.java 5063 2006-03-21 11:21:19 -0800 (Tue, 21 Mar 2006)
0003: * jonathanlocke $ $Revision: 529917 $ $Date: 2006-03-21 11:21:19 -0800 (Tue, 21
0004: * Mar 2006) $
0005: *
0006: * ==============================================================================
0007: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0008: * use this file except in compliance with the License. You may obtain a copy of
0009: * the License at
0010: *
0011: * http://www.apache.org/licenses/LICENSE-2.0
0012: *
0013: * Unless required by applicable law or agreed to in writing, software
0014: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0015: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0016: * License for the specific language governing permissions and limitations under
0017: * the License.
0018: */
0019: package wicket;
0020:
0021: import java.io.Serializable;
0022: import java.util.ArrayList;
0023: import java.util.HashMap;
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.Map.Entry;
0030:
0031: import org.apache.commons.logging.Log;
0032: import org.apache.commons.logging.LogFactory;
0033:
0034: import wicket.application.IClassResolver;
0035: import wicket.authorization.IAuthorizationStrategy;
0036: import wicket.feedback.FeedbackMessage;
0037: import wicket.feedback.FeedbackMessages;
0038: import wicket.request.ClientInfo;
0039: import wicket.session.ISessionStore;
0040: import wicket.util.concurrent.CopyOnWriteArrayList;
0041: import wicket.util.convert.IConverter;
0042: import wicket.util.lang.Objects;
0043: import wicket.util.string.Strings;
0044: import wicket.util.time.Duration;
0045:
0046: /**
0047: * Holds information about a user session, including some fixed number of most
0048: * recent pages (and all their nested component information).
0049: * <ul>
0050: * <li><b>Access via RequestCycle </b>- The Session for a {@link RequestCycle}
0051: * can be retrieved by calling {@link RequestCycle#getSession()}.
0052: *
0053: * <li><b>Access via Component </b>- If a RequestCycle object is not available,
0054: * the Session can be retrieved for a Component by calling
0055: * {@link Component#getSession()}. As currently implemented, each Component
0056: * does not itself have a reference to the session that contains it. However,
0057: * the Page component at the root of the containment hierarchy does have a
0058: * reference to the Session that holds the Page. So
0059: * {@link Component#getSession()} traverses the component hierarchy to the root
0060: * Page and then calls {@link Page#getSession()}.
0061: *
0062: * <li><b>Access via Thread Local </b>- In the odd case where neither a
0063: * RequestCycle nor a Component is available, the currently active Session for
0064: * the calling thread can be retrieved by calling the static method
0065: * Session.get(). This last form should only be used if the first two forms
0066: * cannot be used since thread local access can involve a potentially more
0067: * expensive hash map lookup.
0068: *
0069: * <li><b>Locale </b>- A session has a Locale property to support localization.
0070: * The Locale for a session can be set by calling
0071: * {@link Session#setLocale(Locale)}. The Locale for a Session determines how
0072: * localized resources are found and loaded.
0073: *
0074: * <li><b>Style </b>- Besides having an appearance based on locale, resources
0075: * can also have different looks in the same locale (a.k.a. "skins"). The style
0076: * for a session determines the look which is used within the appopriate locale.
0077: * The session style ("skin") can be set with the setStyle() method.
0078: *
0079: * <li><b>Resource Loading </b>- Based on the Session locale and style,
0080: * searching for resources occurs in the following order (where sourcePath is
0081: * set via the ApplicationSettings object for the current Application, and style
0082: * and locale are Session properties):
0083: * <ul>
0084: * 1. [sourcePath]/name[style][locale].[extension] <br>
0085: * 2. [sourcePath]/name[locale].[extension] <br>
0086: * 3. [sourcePath]/name[style].[extension] <br>
0087: * 4. [sourcePath]/name.[extension] <br>
0088: * 5. [classPath]/name[style][locale].[extension] <br>
0089: * 6. [classPath]/name[locale].[extension] <br>
0090: * 7. [classPath]/name[style].[extension] <br>
0091: * 8. [classPath]/name.[extension] <br>
0092: * </ul>
0093: *
0094: * <li><b>Session Properties </b>- Arbitrary objects can be attached to a
0095: * Session by installing a session factory on your Application class which
0096: * creates custom Session subclasses that have typesafe properties specific to
0097: * the application (see {@link Application} for details). To discourage
0098: * non-typesafe access to Session properties, no setProperty() or getProperty()
0099: * method is provided. In a clustered environment, you should take care to call
0100: * the dirty() method when you change a property or youre own. This way the
0101: * session will be reset again in the http session so that the http session
0102: * knows the session is changed.
0103: *
0104: * <li><b>Class Resolver </b>- Sessions have a class resolver (
0105: * {@link IClassResolver}) implementation that is used to locate classes for
0106: * components such as pages.
0107: *
0108: * <li><b>Page Factory </b>- A pluggable implementation of {@link IPageFactory}
0109: * is used to instantiate pages for the session.
0110: *
0111: * <li><b>Removal </b>- Pages can be removed from the Session forcibly by
0112: * calling remove(Page) or removeAll(), although such an action should rarely be
0113: * necessary.
0114: *
0115: * <li><b>Flash Messages</b>- Flash messages are messages that are stored in
0116: * session and are removed after they are displayed to the user. Session acts as
0117: * a store for these messages because they can last across requests.
0118: *
0119: * @author Jonathan Locke
0120: * @author Eelco Hillenius
0121: * @author Igor Vaynberg (ivaynberg)
0122: */
0123: public abstract class Session implements Serializable {
0124: private static final long serialVersionUID = 1L;
0125:
0126: /** Name of session attribute under which this session is stored */
0127: public static final String SESSION_ATTRIBUTE_NAME = "session";
0128:
0129: /** Prefix for attributes holding page map entries */
0130: static final String pageMapEntryAttributePrefix = "p:";
0131:
0132: /** Thread-local current session. */
0133: private static final ThreadLocal current = new ThreadLocal();
0134:
0135: /** A store for dirty objects for one request */
0136: private static final ThreadLocal dirtyObjects = new ThreadLocal();
0137:
0138: /** Logging object */
0139: private static final Log log = LogFactory.getLog(Session.class);
0140:
0141: /** Attribute prefix for page maps stored in the session */
0142: private static final String pageMapAttributePrefix = "m:";
0143:
0144: /**
0145: * Cached instance of agent info which is typically designated by calling
0146: * {@link RequestCycle#newClientInfo()}.
0147: */
0148: private ClientInfo clientInfo;
0149:
0150: /** The converter instance. */
0151: private transient IConverter converter;
0152:
0153: /** True if session state has been changed */
0154: private transient boolean dirty = false;
0155:
0156: /** The locale to use when loading resources for this session. */
0157: private Locale locale;
0158:
0159: /** A number to generate names for auto create pagemaps */
0160: private int autoCreatePageMapCounter = 0;
0161:
0162: /** A linked list for last used pagemap queue */
0163: private LinkedList/* <PageMap> */usedPageMaps = new LinkedList();
0164:
0165: /** Any special "skin" style to use when loading resources. */
0166: private String style;
0167:
0168: /** feedback messages */
0169: private FeedbackMessages feedbackMessages = new FeedbackMessages(
0170: new CopyOnWriteArrayList());
0171:
0172: private transient Map pageMapsUsedInRequest;
0173:
0174: /** cached id because you can't access the id after session unbound */
0175: private String id = null;
0176:
0177: /**
0178: * Temporary instance of the session store. Should be set on each request as
0179: * it is not supposed to go in the session.
0180: */
0181: private transient ISessionStore sessionStore;
0182:
0183: /** Application level meta data. */
0184: private MetaDataEntry[] metaData;
0185:
0186: /**
0187: * Visitor interface for visiting page maps
0188: *
0189: * @author Jonathan Locke
0190: */
0191: public static interface IPageMapVisitor {
0192: /**
0193: * @param pageMap
0194: * The page map
0195: */
0196: public void pageMap(final PageMap pageMap);
0197: }
0198:
0199: /**
0200: * Get the session for the calling thread.
0201: *
0202: * @return Session for calling thread
0203: */
0204: public static Session get() {
0205: final Session session = (Session) current.get();
0206: if (session == null) {
0207: throw new WicketRuntimeException(
0208: "there is no session attached to current thread "
0209: + Thread.currentThread().getName());
0210: }
0211: return session;
0212: }
0213:
0214: /**
0215: * Checks if the <code>Session</code> threadlocal is set in this thread
0216: *
0217: * @return true if {@link Session#get()} can return the instance of session,
0218: * false otherwise
0219: */
0220: public static boolean exists() {
0221: return current.get() != null;
0222: }
0223:
0224: /**
0225: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0226: * <p>
0227: * Sets session for calling thread.
0228: *
0229: * @param session
0230: * The session
0231: */
0232: public static void set(final Session session) {
0233: if (session == null) {
0234: throw new IllegalArgumentException(
0235: "Argument session can not be null");
0236: }
0237: current.set(session);
0238: }
0239:
0240: /**
0241: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0242: * <p>
0243: * Clears the session for calling thread.
0244: *
0245: */
0246: public static void unset() {
0247: current.set(null);
0248: }
0249:
0250: /**
0251: * Constructor.
0252: *
0253: * @param application
0254: * The application that this is a session of
0255: */
0256: protected Session(final Application application) {
0257: // Set locale to default locale
0258: setLocale(application.getApplicationSettings()
0259: .getDefaultLocale());
0260: }
0261:
0262: /**
0263: * Removes all pages from the session. Although this method should rarely be
0264: * needed, it is available (possibly for security reasons).
0265: */
0266: public final void clear() {
0267: visitPageMaps(new IPageMapVisitor() {
0268: public void pageMap(PageMap pageMap) {
0269: pageMap.clear();
0270: }
0271: });
0272: }
0273:
0274: /**
0275: * Get the application that is currently working with this session.
0276: *
0277: * @return Returns the application.
0278: */
0279: public final Application getApplication() {
0280: return Application.get();
0281: }
0282:
0283: /**
0284: * @return The authorization strategy for this session
0285: */
0286: public IAuthorizationStrategy getAuthorizationStrategy() {
0287: return getApplication().getSecuritySettings()
0288: .getAuthorizationStrategy();
0289: }
0290:
0291: /**
0292: * @return The class resolver for this Session
0293: */
0294: public final IClassResolver getClassResolver() {
0295: return getApplication().getApplicationSettings()
0296: .getClassResolver();
0297: }
0298:
0299: /**
0300: * Gets the client info object for this session. This method lazily gets the
0301: * new agent info object for this session. It uses any cached or set ({@link #setClientInfo(ClientInfo)})
0302: * client info object or uses {@link RequestCycle#newClientInfo()} to get
0303: * the info object based on the current request when no client info object
0304: * was set yet, and then caches the returned object; we can expect the
0305: * client to stay the same for the whole session, and implementations of
0306: * {@link RequestCycle#newClientInfo()} might be relatively expensive.
0307: *
0308: * @return the client info object based on this request
0309: */
0310: public ClientInfo getClientInfo() {
0311: if (clientInfo == null) {
0312: this .clientInfo = RequestCycle.get().newClientInfo();
0313: }
0314: return clientInfo;
0315: }
0316:
0317: /**
0318: * @return The default page map
0319: */
0320: public final PageMap getDefaultPageMap() {
0321: return pageMapForName(PageMap.DEFAULT_NAME, true);
0322: }
0323:
0324: /**
0325: * Gets the unique id for this session from the underlying SessionStore
0326: *
0327: * @return The unique id for this session
0328: */
0329: public final String getId() {
0330: if (id == null) {
0331: id = getSessionStore().getSessionId(
0332: RequestCycle.get().getRequest());
0333:
0334: // we have one?
0335: if (id != null) {
0336: dirty();
0337: }
0338: }
0339: return id;
0340: }
0341:
0342: /**
0343: * Get this session's locale.
0344: *
0345: * @return This session's locale
0346: */
0347: public Locale getLocale() {
0348: return locale;
0349: }
0350:
0351: /**
0352: * Gets metadata for this session using the given key.
0353: *
0354: * @param key
0355: * The key for the data
0356: * @return The metadata
0357: * @see MetaDataKey
0358: */
0359: public final Serializable getMetaData(final MetaDataKey key) {
0360: return key.get(metaData);
0361: }
0362:
0363: /**
0364: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0365: *
0366: * Get the page for the given path.
0367: *
0368: * @param pageMapName
0369: * The name of the page map where the page is
0370: * @param path
0371: * Component path
0372: * @param versionNumber
0373: * The version of the page required
0374: * @return The page based on the first path component (the page id), or null
0375: * if the requested version of the page cannot be found.
0376: */
0377: public final Page getPage(final String pageMapName,
0378: final String path, final int versionNumber) {
0379: if (log.isDebugEnabled()) {
0380: log.debug("Getting page [path = " + path
0381: + ", versionNumber = " + versionNumber + "]");
0382: }
0383:
0384: // Get page map by name, creating the default page map automatically
0385: PageMap pageMap = pageMapForName(pageMapName,
0386: pageMapName == PageMap.DEFAULT_NAME);
0387: if (pageMap != null) {
0388: synchronized (usedPageMaps) // get a lock so be sure that only one
0389: // is made
0390: {
0391: if (pageMapsUsedInRequest == null) {
0392: pageMapsUsedInRequest = new HashMap(3);
0393: }
0394: }
0395: synchronized (pageMapsUsedInRequest) {
0396: long startTime = System.currentTimeMillis();
0397:
0398: // TODO For now only use the setting. Might be extended with
0399: // something overridable on request/ page/ request target level
0400: // later
0401: Duration timeout = Application.get()
0402: .getRequestCycleSettings().getTimeout();
0403:
0404: // Get page entry for id and version
0405: Thread t = (Thread) pageMapsUsedInRequest.get(pageMap);
0406: while (t != null && t != Thread.currentThread()) {
0407: try {
0408: pageMapsUsedInRequest.wait(timeout
0409: .getMilliseconds());
0410: } catch (InterruptedException ex) {
0411: throw new WicketRuntimeException(ex);
0412: }
0413: t = (Thread) pageMapsUsedInRequest.get(pageMap);
0414: if (t != null
0415: && t != Thread.currentThread()
0416: && (startTime + timeout.getMilliseconds()) < System
0417: .currentTimeMillis()) {
0418: // if it is still not the right thread..
0419: // This either points to long running code (a report
0420: // page?) or a deadlock or such
0421: throw new WicketRuntimeException(
0422: "After "
0423: + timeout
0424: + " the Pagemap "
0425: + pageMapName
0426: + " is still locked by: "
0427: + t
0428: + ", giving up trying to get the page for path: "
0429: + path);
0430: }
0431: }
0432: pageMapsUsedInRequest.put(pageMap, Thread
0433: .currentThread());
0434: final String id = Strings.firstPathComponent(path,
0435: Component.PATH_SEPARATOR);
0436: Page page = pageMap.get(Integer.parseInt(id),
0437: versionNumber);
0438: if (page == null) {
0439: pageMapsUsedInRequest.remove(pageMap);
0440: pageMapsUsedInRequest.notifyAll();
0441: }
0442: return page;
0443: }
0444: }
0445: return null;
0446: }
0447:
0448: /**
0449: * @return The page factory for this session
0450: */
0451: public final IPageFactory getPageFactory() {
0452: return getApplication().getSessionSettings().getPageFactory();
0453: }
0454:
0455: /**
0456: * @param page
0457: * The page, or null if no page context is available
0458: * @return The page factory for the page, or the default page factory if
0459: * page was null
0460: */
0461: public final IPageFactory getPageFactory(final Page page) {
0462: if (page != null) {
0463: return page.getPageFactory();
0464: }
0465: return getPageFactory();
0466: }
0467:
0468: /**
0469: * Gets a page map for the given name, automatically creating it if need be.
0470: *
0471: * @param pageMapName
0472: * Name of page map, or null for default page map
0473: * @param autoCreate
0474: * True if the page map should be automatically created if it
0475: * does not exist
0476: * @return PageMap for name
0477: */
0478: public final PageMap pageMapForName(String pageMapName,
0479: final boolean autoCreate) {
0480: PageMap pageMap = (PageMap) getAttribute(attributeForPageMapName(pageMapName));
0481: if (pageMap == null && autoCreate) {
0482: pageMap = newPageMap(pageMapName);
0483: }
0484: return pageMap;
0485: }
0486:
0487: /**
0488: * Automatically creates a page map, giving it a session unique name.
0489: *
0490: * @return Created PageMap
0491: */
0492: public synchronized final PageMap createAutoPageMap() {
0493: return newPageMap(createAutoPageMapName());
0494: }
0495:
0496: /**
0497: * With this call you can create a pagemap name but not create the pagemap
0498: * itself already. It will give the first pagemap name where it couldn't
0499: * find a current pagemap for.
0500: *
0501: * It will return the same name if you call it 2 times in a row.
0502: *
0503: * @return The created pagemap name
0504: */
0505: public synchronized final String createAutoPageMapName() {
0506: String name = "wicket-" + autoCreatePageMapCounter;
0507: PageMap pm = pageMapForName(name, false);
0508: while (pm != null) {
0509: autoCreatePageMapCounter++;
0510: name = "wicket-" + autoCreatePageMapCounter;
0511: pm = pageMapForName(name, false);
0512: }
0513: return name;
0514: }
0515:
0516: /**
0517: * @return A list of all PageMaps in this session.
0518: */
0519: public final List getPageMaps() {
0520: final List list = new ArrayList();
0521: for (final Iterator iterator = getAttributeNames().iterator(); iterator
0522: .hasNext();) {
0523: final String attribute = (String) iterator.next();
0524: if (attribute.startsWith(pageMapAttributePrefix)) {
0525: list.add(getAttribute(attribute));
0526: }
0527: }
0528: return list;
0529: }
0530:
0531: /**
0532: * @return Size of this session, including all the pagemaps it contains
0533: */
0534: public final long getSizeInBytes() {
0535: long size = Objects.sizeof(this );
0536: for (final Iterator iterator = getPageMaps().iterator(); iterator
0537: .hasNext();) {
0538: final PageMap pageMap = (PageMap) iterator.next();
0539: size += pageMap.getSizeInBytes();
0540: }
0541: return size;
0542: }
0543:
0544: /**
0545: * Get the style (see {@link wicket.Session}).
0546: *
0547: * @return Returns the style (see {@link wicket.Session})
0548: */
0549: public final String getStyle() {
0550: return style;
0551: }
0552:
0553: /**
0554: * Set the session for each PageMap
0555: */
0556: public final void init() {
0557: // Set session on each page map
0558: visitPageMaps(new IPageMapVisitor() {
0559: public void pageMap(PageMap pageMap) {
0560: if (log.isDebugEnabled()) {
0561: log
0562: .debug("updateSession(): Attaching session to PageMap "
0563: + pageMap);
0564: }
0565: pageMap.setSession(Session.this );
0566: }
0567: });
0568: }
0569:
0570: /**
0571: * Invalidates this session.
0572: */
0573: public void invalidate() {
0574: getSessionStore().invalidate(RequestCycle.get().getRequest());
0575: }
0576:
0577: /**
0578: * Creates a new page map with a given name
0579: *
0580: * @param name
0581: * The name for the new page map
0582: * @return The newly created page map
0583: */
0584: public final PageMap newPageMap(final String name) {
0585: // Check that session doesn't have too many page maps already
0586: final int maxPageMaps = getApplication().getSessionSettings()
0587: .getMaxPageMaps();
0588: if (usedPageMaps.size() >= maxPageMaps) {
0589: PageMap pm = (PageMap) usedPageMaps.getFirst();
0590: pm.remove();
0591: }
0592:
0593: // Create new page map
0594: final PageMap pageMap = new PageMap(name, this );
0595: setAttribute(attributeForPageMapName(name), pageMap);
0596: dirty();
0597: return pageMap;
0598: }
0599:
0600: /**
0601: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0602: *
0603: * Creates a new RequestCycle for the given request and response using the
0604: * session's request cycle factory.
0605: *
0606: * @param request
0607: * The request
0608: * @param response
0609: * The response
0610: * @return The new request cycle.
0611: */
0612: public final RequestCycle newRequestCycle(final Request request,
0613: final Response response) {
0614: return getRequestCycleFactory().newRequestCycle(this , request,
0615: response);
0616: }
0617:
0618: /**
0619: * @param pageMap
0620: * Page map to remove
0621: */
0622: public final void removePageMap(final PageMap pageMap) {
0623: usedPageMaps.remove(pageMap);
0624: removeAttribute(attributeForPageMapName(pageMap.getName()));
0625: dirty();
0626: }
0627:
0628: /**
0629: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0630: * <p>
0631: * Sets the application that this session is associated with.
0632: *
0633: * @param application
0634: * The application
0635: */
0636: public final void setApplication(final Application application) {
0637: }
0638:
0639: /**
0640: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0641: * <p>
0642: * Sets the client info object for this session. This will only work when
0643: * {@link #getClientInfo()} is not overriden.
0644: *
0645: * @param clientInfo
0646: * the client info object
0647: */
0648: public final void setClientInfo(ClientInfo clientInfo) {
0649: this .clientInfo = clientInfo;
0650: dirty();
0651: }
0652:
0653: /**
0654: * Set the locale for this session.
0655: *
0656: * @param locale
0657: * New locale
0658: */
0659: public final void setLocale(final Locale locale) {
0660: if (locale == null) {
0661: throw new IllegalArgumentException(
0662: "Parameter 'locale' must not be null");
0663: }
0664: this .locale = locale;
0665: this .converter = null;
0666: dirty();
0667: }
0668:
0669: /**
0670: * Sets the metadata for this session using the given key. If the metadata
0671: * object is not of the correct type for the metadata key, an
0672: * IllegalArgumentException will be thrown. For information on creating
0673: * MetaDataKeys, see {@link MetaDataKey}.
0674: *
0675: * @param key
0676: * The singleton key for the metadata
0677: * @param object
0678: * The metadata object
0679: * @throws IllegalArgumentException
0680: * @see MetaDataKey
0681: */
0682: public final void setMetaData(final MetaDataKey key,
0683: final Serializable object) {
0684: metaData = key.set(metaData, object);
0685: }
0686:
0687: /**
0688: * Set the style (see {@link wicket.Session}).
0689: *
0690: * @param style
0691: * The style to set.
0692: * @return the Session object
0693: */
0694: public final Session setStyle(final String style) {
0695: this .style = style;
0696: dirty();
0697: return this ;
0698: }
0699:
0700: /**
0701: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0702: * <p>
0703: * The page will be 'touched' in the session. If it wasn't added yet to the
0704: * pagemap, it will be added to the page map else it will set this page to
0705: * the front.
0706: *
0707: * If another page was removed because of this it will be cleaned up.
0708: *
0709: * @param page
0710: */
0711: public final void touch(Page page) {
0712: // Touch the page in its pagemap.
0713: page.getPageMap().put(page);
0714: }
0715:
0716: /**
0717: * @param visitor
0718: * The visitor to call at each Page in this PageMap.
0719: */
0720: public final void visitPageMaps(final IPageMapVisitor visitor) {
0721: for (final Iterator iterator = getAttributeNames().iterator(); iterator
0722: .hasNext();) {
0723: final String attribute = (String) iterator.next();
0724: if (attribute.startsWith(pageMapAttributePrefix)) {
0725: visitor.pageMap((PageMap) getAttribute(attribute));
0726: }
0727: }
0728: }
0729:
0730: /**
0731: * Registers an informational feedback message for this session
0732: *
0733: * @param message
0734: * The feedback message
0735: */
0736: public final void info(final String message) {
0737: addFeedbackMessage(message, FeedbackMessage.INFO);
0738: }
0739:
0740: /**
0741: * Registers a warning feedback message for this session
0742: *
0743: * @param message
0744: * The feedback message
0745: */
0746: public final void warn(final String message) {
0747: addFeedbackMessage(message, FeedbackMessage.WARNING);
0748: }
0749:
0750: /**
0751: * Registers an error feedback message for this session
0752: *
0753: * @param message
0754: * The feedback message
0755: */
0756: public final void error(final String message) {
0757: addFeedbackMessage(message, FeedbackMessage.ERROR);
0758: }
0759:
0760: /**
0761: * Gets feedback messages stored in session
0762: *
0763: * @return unmodifiable list of feedback messages
0764: */
0765: public final FeedbackMessages getFeedbackMessages() {
0766: return feedbackMessages;
0767: }
0768:
0769: /**
0770: * Gets the converter instance. This method returns the cached converter for
0771: * the current locale. Whenever the locale is changed, the cached value is
0772: * cleared and the converter will be recreated for the new locale on a next
0773: * request.
0774: *
0775: * @return the converter
0776: */
0777: public final IConverter getConverter() {
0778: if (converter == null) {
0779: // Let the factory create a new converter
0780: converter = getApplication().getApplicationSettings()
0781: .getConverterFactory().newConverter(getLocale());
0782: }
0783: return converter;
0784: }
0785:
0786: /**
0787: * Adds a feedback message to the list of messages
0788: *
0789: * @param message
0790: * @param level
0791: *
0792: */
0793: private void addFeedbackMessage(String message, int level) {
0794: getFeedbackMessages().add(null, message, level);
0795: dirty();
0796: }
0797:
0798: /**
0799: * Any detach logic for session subclasses. This is called on the end of
0800: * handling a request, when the RequestCycle is about to be detached from
0801: * the current thread.
0802: */
0803: protected void detach() {
0804: }
0805:
0806: /**
0807: * Marks session state as dirty
0808: */
0809: protected final void dirty() {
0810: this .dirty = true;
0811: }
0812:
0813: /**
0814: * Gets the attribute value with the given name
0815: *
0816: * @param name
0817: * The name of the attribute to store
0818: * @return The value of the attribute
0819: */
0820: protected final Object getAttribute(final String name) {
0821: RequestCycle cycle = RequestCycle.get();
0822: if (cycle != null) {
0823: return getSessionStore().getAttribute(cycle.getRequest(),
0824: name);
0825: }
0826: return null;
0827: }
0828:
0829: /**
0830: * @return List of attributes for this session
0831: */
0832: protected final List getAttributeNames() {
0833: RequestCycle cycle = RequestCycle.get();
0834: if (cycle != null) {
0835: return getSessionStore().getAttributeNames(
0836: cycle.getRequest());
0837: }
0838: return null;
0839: }
0840:
0841: /**
0842: * @return Request cycle factory for this kind of session.
0843: */
0844: protected abstract IRequestCycleFactory getRequestCycleFactory();
0845:
0846: /**
0847: * Gets the session store.
0848: *
0849: * @return the session store
0850: */
0851: protected ISessionStore getSessionStore() {
0852: if (sessionStore == null) {
0853: sessionStore = getApplication().getSessionStore();
0854: }
0855: return sessionStore;
0856: }
0857:
0858: /**
0859: * Removes the attribute with the given name.
0860: *
0861: * @param name
0862: * the name of the attribute to remove
0863: */
0864: protected final void removeAttribute(String name) {
0865: RequestCycle cycle = RequestCycle.get();
0866: if (cycle != null) {
0867: getSessionStore().removeAttribute(cycle.getRequest(), name);
0868: }
0869: }
0870:
0871: /**
0872: * Adds or replaces the attribute with the given name and value.
0873: *
0874: * @param name
0875: * The name of the attribute
0876: * @param value
0877: * The value of the attribute
0878: */
0879: protected final void setAttribute(String name, Object value) {
0880: RequestCycle cycle = RequestCycle.get();
0881: if (cycle == null) {
0882: throw new WicketRuntimeException(
0883: "Can not set the attribute. No RequestCycle available");
0884: }
0885:
0886: ISessionStore store = getSessionStore();
0887: Request request = cycle.getRequest();
0888:
0889: // extra check on session binding event
0890: if (value == this ) {
0891: Object current = store.getAttribute(request, name);
0892: if (current == null) {
0893: // this is a new instance. wherever it came from, bind the
0894: // session now
0895: store.bind(request, (Session) value);
0896: }
0897: }
0898:
0899: // Set the actual attribute
0900: store.setAttribute(request, name, value);
0901: }
0902:
0903: /**
0904: * Updates the session, e.g. for replication purposes.
0905: */
0906: protected void update() {
0907: // If state is dirty
0908: if (dirty) {
0909: if (log.isDebugEnabled()) {
0910: log.debug("update: Session is dirty. Replicating.");
0911: }
0912:
0913: // State is no longer dirty
0914: this .dirty = false;
0915:
0916: // Set attribute.
0917: setAttribute(SESSION_ATTRIBUTE_NAME, this );
0918: } else {
0919: if (log.isDebugEnabled()) {
0920: log.debug("update: Session not dirty.");
0921: }
0922: }
0923: }
0924:
0925: /**
0926: * Removes any rendered feedback messages as well as compacts memory. This
0927: * method is usually called at the end of the request cycle processing.
0928: */
0929: final void cleanupFeedbackMessages() {
0930: int size = feedbackMessages.size();
0931: feedbackMessages.clearRendered();
0932:
0933: // the session is dirty when the list of feedback messages was changed
0934: if (size != feedbackMessages.size()) {
0935: dirty();
0936: }
0937: }
0938:
0939: /**
0940: * @param page
0941: * The page to add to dirty objects list
0942: */
0943: void dirtyPage(final Page page) {
0944: List dirtyObjects = getDirtyObjectsList();
0945: if (!dirtyObjects.contains(page)) {
0946: dirtyObjects.add(page);
0947: }
0948: }
0949:
0950: /**
0951: * INTERNAL API. The request cycle when detached will call this.
0952: *
0953: */
0954: final void requestDetached() {
0955: // Go through all dirty entries, replicating any dirty objects
0956: List dirtyObjects = (List) Session.dirtyObjects.get();
0957: if (dirtyObjects != null) {
0958: for (final Iterator iterator = dirtyObjects.iterator(); iterator
0959: .hasNext();) {
0960: String attribute = null;
0961: Object object = iterator.next();
0962: if (object instanceof Page) {
0963: final Page page = (Page) object;
0964: if (page.isStateless()) {
0965: // check, can it be that stateless pages where added to
0966: // the session?
0967: // and should be removed now?
0968: continue;
0969: }
0970: attribute = page.getPageMap().attributeForId(
0971: page.getNumericId());
0972: if (getAttribute(attribute) == null) {
0973: // page removed by another thread. don't add it again.
0974: continue;
0975: }
0976: object = page.getPageMapEntry();
0977: } else if (object instanceof PageMap) {
0978: attribute = attributeForPageMapName(((PageMap) object)
0979: .getName());
0980: }
0981:
0982: setAttribute(attribute, object);
0983: }
0984: Session.dirtyObjects.set(null);
0985: }
0986: if (pageMapsUsedInRequest != null) {
0987: synchronized (pageMapsUsedInRequest) {
0988: Thread t = Thread.currentThread();
0989: Iterator it = pageMapsUsedInRequest.entrySet()
0990: .iterator();
0991: while (it.hasNext()) {
0992: Entry entry = (Entry) it.next();
0993: if (entry.getValue() == t) {
0994: it.remove();
0995: }
0996: }
0997: pageMapsUsedInRequest.notifyAll();
0998: }
0999: }
1000: }
1001:
1002: /**
1003: * @param map
1004: * The page map to add to dirty objects list
1005: */
1006: void dirtyPageMap(final PageMap map) {
1007: if (!map.isDefault()) {
1008: usedPageMaps.remove(map);
1009: usedPageMaps.addLast(map);
1010: }
1011: List dirtyObjects = getDirtyObjectsList();
1012: if (!dirtyObjects.contains(map)) {
1013: dirtyObjects.add(map);
1014: }
1015: }
1016:
1017: /**
1018: * @param pageMapName
1019: * Name of page map
1020: * @return Session attribute holding page map
1021: */
1022: private final String attributeForPageMapName(
1023: final String pageMapName) {
1024: return pageMapAttributePrefix + pageMapName;
1025: }
1026:
1027: /**
1028: * @return The current thread dirty objects list
1029: */
1030: List getDirtyObjectsList() {
1031: List list = (List) dirtyObjects.get();
1032: if (list == null) {
1033: list = new ArrayList(4);
1034: dirtyObjects.set(list);
1035: }
1036: return list;
1037: }
1038: }
|