All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.Session Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.wicket.application.IClassResolver;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.feedback.FeedbackMessages;
import org.apache.wicket.protocol.http.IgnoreAjaxRequestException;
import org.apache.wicket.request.ClientInfo;
import org.apache.wicket.session.ISessionStore;
import org.apache.wicket.util.lang.Objects;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Holds information about a user session, including some fixed number of most recent pages (and all
 * their nested component information).
 * 
    *
  • Access via RequestCycle - The Session for a {@link RequestCycle} can be retrieved by * calling {@link RequestCycle#getSession()}. * *
  • Access via Component - If a RequestCycle object is not available, the Session can be * retrieved for a Component by calling {@link Component#getSession()}. As currently implemented, * each Component does not itself have a reference to the session that contains it. However, the * Page component at the root of the containment hierarchy does have a reference to the Session that * holds the Page. So {@link Component#getSession()} traverses the component hierarchy to the root * Page and then calls {@link Page#getSession()}. * *
  • Access via Thread Local - In the odd case where neither a RequestCycle nor a Component * is available, the currently active Session for the calling thread can be retrieved by calling the * static method Session.get(). This last form should only be used if the first two forms cannot be * used since thread local access can involve a potentially more expensive hash map lookup. * *
  • Locale - A session has a Locale property to support localization. The Locale for a * session can be set by calling {@link Session#setLocale(Locale)}. The Locale for a Session * determines how localized resources are found and loaded. * *
  • Style - Besides having an appearance based on locale, resources can also have * different looks in the same locale (a.k.a. "skins"). The style for a session determines the look * which is used within the appropriate locale. The session style ("skin") can be set with the * setStyle() method. * *
  • Resource Loading - Based on the Session locale and style, searching for resources * occurs in the following order (where sourcePath is set via the ApplicationSettings object for the * current Application, and style and locale are Session properties): *
      * 1. [sourcePath]/name[style][locale].[extension]
      * 2. [sourcePath]/name[locale].[extension]
      * 3. [sourcePath]/name[style].[extension]
      * 4. [sourcePath]/name.[extension]
      * 5. [classPath]/name[style][locale].[extension]
      * 6. [classPath]/name[locale].[extension]
      * 7. [classPath]/name[style].[extension]
      * 8. [classPath]/name.[extension]
      *
    * *
  • Session Properties - Arbitrary objects can be attached to a Session by installing a * session factory on your Application class which creates custom Session subclasses that have * typesafe properties specific to the application (see {@link Application} for details). To * discourage non-typesafe access to Session properties, no setProperty() or getProperty() method is * provided. In a clustered environment, you should take care to call the dirty() method when you * change a property on your own. This way the session will be reset again in the http session so * that the http session knows the session is changed. * *
  • Class Resolver - Sessions have a class resolver ( {@link IClassResolver}) * implementation that is used to locate classes for components such as pages. * *
  • Page Factory - A pluggable implementation of {@link IPageFactory} is used to * instantiate pages for the session. * *
  • Removal - Pages can be removed from the Session forcibly by calling remove(Page) or * removeAll(), although such an action should rarely be necessary. * *
  • Flash Messages- Flash messages are messages that are stored in session and are removed * after they are displayed to the user. Session acts as a store for these messages because they can * last across requests. * * @author Jonathan Locke * @author Eelco Hillenius * @author Igor Vaynberg (ivaynberg) */ public abstract class Session implements IClusterable { /** * Visitor interface for visiting page maps * * @author Jonathan Locke */ public static interface IPageMapVisitor { /** * @param pageMap * The page map */ public void pageMap(final IPageMap pageMap); } /** * meta data for recording map map access. */ public static final class PageMapAccessMetaData implements IClusterable { private static final long serialVersionUID = 1L; Set pageMapNames = new HashSet(2); /** * @param pagemap * the pagemap to add as used. * @return the boolean if it was added (didn't already contain the pagemap) */ public boolean add(IPageMap pagemap) { return pageMapNames.add(pagemap.getName()); } } /** a sequence used for whenever something session-specific needs a unique value */ private int sequence = 1; /** meta data key for missing body tags logging. */ public static final MetaDataKey PAGEMAP_ACCESS_MDK = new MetaDataKey() { private static final long serialVersionUID = 1L; }; /** Name of session attribute under which this session is stored */ public static final String SESSION_ATTRIBUTE_NAME = "session"; /** Thread-local current session. */ private static final ThreadLocal current = new ThreadLocal(); /** A store for dirty objects for one request */ private static final ThreadLocal> dirtyObjects = new ThreadLocal>(); /** Logging object */ private static final Logger log = LoggerFactory.getLogger(Session.class); /** Attribute prefix for page maps stored in the session */ private static final String pageMapAttributePrefix = "m:"; private static final long serialVersionUID = 1L; /** A store for touched pages for one request */ private static final ThreadLocal> touchedPages = new ThreadLocal>(); /** Prefix for attributes holding page map entries */ static final String pageMapEntryAttributePrefix = "p:"; /** */ private int pageIdCounter = 0; /** * Checks if the Session threadlocal is set in this thread * * @return true if {@link Session#get()} can return the instance of session, false otherwise */ public static boolean exists() { return current.get() != null; } /** * Locate the session for the client of this request in the {@link ISessionStore} or create a * new one and attach it when none could be located and sets it as the current instance for this * thread. Typically, clients never touch this method, but rather use {@link Session#get()}, * which does the locating implicitly when not yet set as a thread local. * * @return The session for the client of this request or a new, unbound */ public static final Session findOrCreate() { RequestCycle requestCycle = RequestCycle.get(); if (requestCycle == null) { throw new IllegalStateException( "you can only locate or create sessions in the context of a request cycle"); } Response response = requestCycle.getResponse(); Request request = requestCycle.getRequest(); return findOrCreate(request, response); } /** * @param response * @param request * @return The Session that is found in the current request or created if not. */ public static Session findOrCreate(Request request, Response response) { Application application = Application.get(); ISessionStore sessionStore = application.getSessionStore(); Session session = sessionStore.lookup(request); if (session == null) { // Create session using session factory session = application.newSession(request, response); dirtyObjects.set(null); touchedPages.set(null); } // set thread local set(session); return session; } /** * Get the session for the calling thread. * * @return Session for calling thread */ public static Session get() { Session session = current.get(); if (session == null) { session = findOrCreate(); } return session; } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. *

    * Sets session for calling thread. Also triggers {@link #attach()} being called. * * @param session * The session */ public static void set(final Session session) { if (session == null) { throw new IllegalArgumentException("Argument session can not be null"); } current.set(session); try { // execute any attach logic now session.attach(); } catch (RuntimeException e) { current.set(null); } } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. *

    * Clears the session for calling thread. * */ public static void unset() { current.set(null); } /** A number to generate names for auto create pagemaps */ private int autoCreatePageMapCounter = 0; /** * Cached instance of agent info which is typically designated by calling * {@link RequestCycle#newClientInfo()}. */ private ClientInfo clientInfo; /** True if session state has been changed */ private transient boolean dirty = false; /** feedback messages */ private final FeedbackMessages feedbackMessages = new FeedbackMessages(); /** cached id because you can't access the id after session unbound */ private String id = null; /** The locale to use when loading resources for this session. */ private Locale locale; /** Application level meta data. */ private MetaDataEntry[] metaData; /** * We need to know both thread that keeps the pagemap lock and the RequestCycle */ private static class PageMapsUsedInRequestEntry { Thread thread; RequestCycle requestCycle; }; private transient Map pageMapsUsedInRequest; /** True, if session has been invalidated */ private transient boolean sessionInvalidated = false; /** * Temporary instance of the session store. Should be set on each request as it is not supposed * to go in the session. */ private transient ISessionStore sessionStore; /** Any special "skin" style to use when loading resources. */ private String style; /** * Holds attributes for sessions that are still temporary/ not bound to a session store. Only * used when {@link #isTemporary()} is true. *

    * Note: this doesn't have to be synchronized, as the only time when this map is used is when a * session is temporary, in which case it won't be shared between requests (it's a per request * instance). *

    */ private transient Map temporarySessionAttributes; /** A linked list for last used pagemap names queue */ private final LinkedList usedPageMapNames = new LinkedList(); /** * Constructor. Note that {@link RequestCycle} is not available until this constructor returns. * * @param request * The current request */ public Session(Request request) { locale = request.getLocale(); if (locale == null) { throw new IllegalStateException( "Request#getLocale() cannot return null, request has to have a locale set on it"); } } /** * Constructor. Note that {@link RequestCycle} is not available until this constructor returns. * * @deprecated Use #Session(Request) * * @param application * The application that this is a session of * @param request * The current request */ @Deprecated protected Session(Application application, Request request) { this(request); } /** * Force binding this session to the application's {@link ISessionStore session store} if not * already done so. *

    * A Wicket application can operate in a session-less mode as long as stateless pages are used. * Session objects will be then created for each request, but they will only live for that * request. You can recognize temporary sessions by calling {@link #isTemporary()} which * basically checks whether the session's id is null. Hence, temporary sessions have no session * id. *

    *

    * By calling this method, the session will be bound (made not-temporary) if it was not bound * yet. It is useful for cases where you want to be absolutely sure this session object will be * available in next requests. If the session was already bound ( * {@link ISessionStore#lookup(Request) returns a session}), this call will be a noop. *

    */ public final void bind() { // If there is no request cycle then this is not a normal request but for example a last // modified call. if (RequestCycle.get() == null) { return; } ISessionStore store = getSessionStore(); Request request = RequestCycle.get().getRequest(); if (store.lookup(request) == null) { // explicitly create a session id = store.getSessionId(request, true); // bind it store.bind(request, this); if (temporarySessionAttributes != null) { for (Entry entry : temporarySessionAttributes.entrySet()) { store.setAttribute(request, String.valueOf(entry.getKey()), entry.getValue()); } temporarySessionAttributes = null; } } } /** * Cleans up all rendered feedback messages and any unrendered, dangling feedback messages there * may be left after that. */ public abstract void cleanupFeedbackMessages(); /** * Removes all pages from the session. Although this method should rarely be needed, it is * available (possibly for security reasons). */ public final void clear() { visitPageMaps(new IPageMapVisitor() { public void pageMap(IPageMap pageMap) { pageMap.clear(); } }); } /** * Automatically creates a page map, giving it a session unique name. * * @return Created PageMap */ public final IPageMap createAutoPageMap() { return newPageMap(createAutoPageMapName()); } protected int currentCreateAutoPageMapCounter() { return autoCreatePageMapCounter; } protected void incrementCreateAutoPageMapCounter() { ++autoCreatePageMapCounter; } /** * With this call you can create a pagemap name but not create the pagemap itself already. It * will give the first pagemap name where it couldn't find a current pagemap for. * * It will return the same name if you call it 2 times in a row. * * @return The created pagemap name */ public synchronized final String createAutoPageMapName() { String name = getAutoPageMapNamePrefix() + currentCreateAutoPageMapCounter() + getAutoPageMapNameSuffix(); IPageMap pm = pageMapForName(name, false); while (pm != null) { incrementCreateAutoPageMapCounter(); name = getAutoPageMapNamePrefix() + currentCreateAutoPageMapCounter() + getAutoPageMapNameSuffix(); pm = pageMapForName(name, false); } return name; } /** * @return The prefixed string default "wicket-". */ protected String getAutoPageMapNamePrefix() { return "wicket-"; } /** * @return The suffix default an empty string. */ protected String getAutoPageMapNameSuffix() { return ""; } /** * Registers an error feedback message for this session * * @param message * The feedback message */ public final void error(final String message) { addFeedbackMessage(message, FeedbackMessage.ERROR); } /** * Get the application that is currently working with this session. * * @return Returns the application. */ public final Application getApplication() { return Application.get(); } /** * @return The authorization strategy for this session */ public IAuthorizationStrategy getAuthorizationStrategy() { return getApplication().getSecuritySettings().getAuthorizationStrategy(); } /** * @return The class resolver for this Session */ public final IClassResolver getClassResolver() { return getApplication().getApplicationSettings().getClassResolver(); } /** * Gets the client info object for this session. This method lazily gets the new agent info * object for this session. It uses any cached or set ({@link #setClientInfo(ClientInfo)}) * client info object or uses {@link RequestCycle#newClientInfo()} to get the info object based * on the current request when no client info object was set yet, and then caches the returned * object; we can expect the client to stay the same for the whole session, and implementations * of {@link RequestCycle#newClientInfo()} might be relatively expensive. * * @return the client info object based on this request */ public ClientInfo getClientInfo() { if (clientInfo == null) { clientInfo = RequestCycle.get().newClientInfo(); } return clientInfo; } /** * @return The default page map */ public final IPageMap getDefaultPageMap() { return pageMapForName(PageMap.DEFAULT_NAME, true); } /** * Gets feedback messages stored in session * * @return unmodifiable list of feedback messages */ public final FeedbackMessages getFeedbackMessages() { return feedbackMessages; } /** * Gets the unique id for this session from the underlying SessionStore. May be null if a * concrete session is not yet created. * * @return The unique id for this session or null if it is a temporary session */ public final String getId() { if (id == null) { id = getSessionStore().getSessionId(RequestCycle.get().getRequest(), false); // we have one? if (id != null) { dirty(); } } return id; } /** * Get this session's locale. * * @return This session's locale */ public Locale getLocale() { return locale; } /** * Gets metadata for this session using the given key. * * @param key * The key for the data * @param * The type of the metadata. * @return The metadata * @see MetaDataKey */ public final synchronized M getMetaData(final MetaDataKey key) { return key.get(metaData); } /** * When a regular request on certain page with certain version is being processed, we don't * allow ajax requests to same page and version. * * @param lockedRequestCycle * @return whether current request is valid or should be discarded */ protected boolean isCurrentRequestValid(RequestCycle lockedRequestCycle) { return true; } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. * * Returns the page with given id and versionNumber. It keeps asking pageMaps for given page * until it finds one that contains it. * * @param pageId * @param versionNumber * @return The page of that pageid and version, null if not found */ public final Page getPage(final int pageId, final int versionNumber) { if (Application.get().getSessionSettings().isPageIdUniquePerSession() == false) { throw new IllegalStateException( "To call this method ISessionSettings.setPageIdUniquePerSession must be set to true"); } List pageMaps = getPageMaps(); for (IPageMap pageMap : pageMaps) { if (pageMap.containsPage(pageId, versionNumber)) { return getPage(pageMap.getName(), "" + pageId, versionNumber); } } return null; } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. * * Get the page for the given path. * * FIXME javadoc - where does it look? where does it get the page from? * * @param pageMapName * The name of the page map where the page is * @param componentPath * Component path * @param versionNumber * The version of the page required * @return The page based on the first path component (the page id), or null if the requested * version of the page cannot be found. */ public final Page getPage(final String pageMapName, final String componentPath, final int versionNumber) { if (log.isDebugEnabled()) { log.debug("Getting page [path = " + componentPath + ", versionNumber = " + versionNumber + "]"); } // Get page map by name, creating the default page map automatically IPageMap pageMap = pageMapForName(pageMapName, pageMapName == PageMap.DEFAULT_NAME); if (pageMap != null) { synchronized (usedPageMapNames) // get a lock so be sure that only one // is made { if (pageMapsUsedInRequest == null) { pageMapsUsedInRequest = new HashMap(3); } } synchronized (pageMapsUsedInRequest) { long startTime = System.currentTimeMillis(); // TODO For now only use the setting. Might be extended with // something overridable on request/ page/ request target level // later Duration timeout = Application.get().getRequestCycleSettings().getTimeout(); PageMapsUsedInRequestEntry entry = pageMapsUsedInRequest.get(pageMap); // Get page entry for id and version Thread t = entry != null ? entry.thread : null; while (t != null && t != Thread.currentThread()) { if (isCurrentRequestValid(entry.requestCycle) == false) { // we need to ignore this request. That's because it is // an ajax request // while regular page request is being processed throw new IgnoreAjaxRequestException(); } try { pageMapsUsedInRequest.wait(timeout.getMilliseconds()); } catch (InterruptedException ex) { throw new WicketRuntimeException(ex); } entry = pageMapsUsedInRequest.get(pageMap); t = entry != null ? entry.thread : null; if (t != null && t != Thread.currentThread() && (startTime + timeout.getMilliseconds()) < System.currentTimeMillis()) { AppendingStringBuffer asb = new AppendingStringBuffer(100); asb.append("After " + timeout + " the Pagemap " + pageMapName + " is still locked by: " + t + ", giving up trying to get the page for path: " + componentPath); // if it is still not the right thread.. // This either points to long running code (a report // page?) or a deadlock or such WicketRuntimeException ex = new WicketRuntimeException(asb.toString()); ex.setStackTrace(t.getStackTrace()); throw ex; } } PageMapsUsedInRequestEntry newEntry = new PageMapsUsedInRequestEntry(); newEntry.thread = Thread.currentThread(); newEntry.requestCycle = RequestCycle.get(); pageMapsUsedInRequest.put(pageMap, newEntry); final String id = Strings.firstPathComponent(componentPath, Component.PATH_SEPARATOR); Page page = pageMap.get(Integer.parseInt(id), versionNumber); if (page == null) { pageMapsUsedInRequest.remove(pageMap); pageMapsUsedInRequest.notifyAll(); } else { // attach the page now. page.onPageAttached(); touch(page); } return page; } } return null; } /** * @return The page factory for this session */ public final IPageFactory getPageFactory() { return getApplication().getSessionSettings().getPageFactory(); } /** * @return A list of all PageMaps in this session. */ public final List getPageMaps() { final List list = new ArrayList(); for (String attribute : getAttributeNames()) { if (attribute.startsWith(pageMapAttributePrefix)) { list.add((IPageMap)getAttribute(attribute)); } } // there is a small chance another thread removes the pagemap while we are iterating the // attributes and we end up with null in our list Iterator maps = list.iterator(); while (maps.hasNext()) { if (maps.next() == null) { maps.remove(); } } Collections.sort(list, new LruComparator()); return list; } /** * Sorting page maps respecting the least recently used sequence. */ private class LruComparator implements Comparator { public int compare(IPageMap pg1, IPageMap pg2) { Integer pg1Index = usedPageMapNames.indexOf(pg1.getName()); Integer pg2Index = usedPageMapNames.indexOf(pg2.getName()); return pg1Index.compareTo(pg2Index); } } /** * @return Size of this session, including all the pagemaps it contains */ public final long getSizeInBytes() { long size = Objects.sizeof(this); for (IPageMap pageMap : getPageMaps()) { size += pageMap.getSizeInBytes(); } return size; } /** * Get the style (see {@link org.apache.wicket.Session}). * * @return Returns the style (see {@link org.apache.wicket.Session}) */ public final String getStyle() { return style; } /** * Registers an informational feedback message for this session * * @param message * The feedback message */ public final void info(final String message) { addFeedbackMessage(message, FeedbackMessage.INFO); } /** * Invalidates this session at the end of the current request. If you need to invalidate the * session immediately, you can do this by calling invalidateNow(), however this will remove all * Wicket components from this session, which means that you will no longer be able to work with * them. */ public void invalidate() { sessionInvalidated = true; } /** * Invalidates this session immediately. Calling this method will remove all Wicket components * from this session, which means that you will no longer be able to work with them. */ public void invalidateNow() { sessionInvalidated = true; // set this for isSessionInvalidated getSessionStore().invalidate(RequestCycle.get().getRequest()); } /** * Replaces the underlying (Web)Session, invalidating the current one and creating a new one. By * calling {@link ISessionStore#invalidate(Request)} and {@link #bind()} *

    * Call upon login to protect against session fixation. * * @see "http://www.owasp.org/index.php/Session_Fixation" */ public void replaceSession() { getSessionStore().invalidate(RequestCycle.get().getRequest()); bind(); } /** * Whether the session is invalid now, or will be invalidated by the end of the request. Clients * should rarely need to use this method if ever. * * @return Whether the session is invalid when the current request is done * * @see #invalidate() * @see #invalidateNow() */ public final boolean isSessionInvalidated() { return sessionInvalidated; } /** * Whether this session is temporary. A Wicket application can operate in a session-less mode as * long as stateless pages are used. If this session object is temporary, it will not be * available on a next request. * * @return Whether this session is temporary (which is the same as it's id being null) */ public final boolean isTemporary() { return getId() == null; } /** * Creates a new page map with a given name * * @param name * The name for the new page map * @return The newly created page map */ public final IPageMap newPageMap(final String name) { // Check that session doesn't have too many page maps already, if so, evict final int maxPageMaps = getApplication().getSessionSettings().getMaxPageMaps(); synchronized (usedPageMapNames) { List usedPageMaps = getPageMaps(); int excessPagemaps = (usedPageMaps.size() + 1) - maxPageMaps;/* * plus 1 meaning the new * one we are about to add */ if (excessPagemaps > 0) { for (int i = 0; i < excessPagemaps; i++) { usedPageMaps.get(i).remove(); } } } // Create new page map final IPageMap pageMap = getSessionStore().createPageMap(name); setAttribute(attributeForPageMapName(name), pageMap); // marking it as the most recently used dirtyPageMap(pageMap); dirty(); return pageMap; } /** * Gets a page map for the given name, automatically creating it if need be. * * @param pageMapName * Name of page map, or null for default page map * @param autoCreate * True if the page map should be automatically created if it does not exist * @return PageMap for name */ public final IPageMap pageMapForName(String pageMapName, final boolean autoCreate) { IPageMap pageMap = (IPageMap)getAttribute(attributeForPageMapName(pageMapName)); if (pageMap == null && autoCreate) { pageMap = newPageMap(pageMapName); } return pageMap; } /** * @param pageMap * Page map to remove */ public final void removePageMap(final IPageMap pageMap) { PageMapAccessMetaData pagemapMetaData = getMetaData(PAGEMAP_ACCESS_MDK); if (pagemapMetaData != null) { pagemapMetaData.pageMapNames.remove(pageMap.getName()); } synchronized (usedPageMapNames) { usedPageMapNames.remove(pageMap.getName()); } // the page map also needs to be removed from the dirty objects list or // the requestDetached method will end up adding it back into session getDirtyObjectsList().remove(pageMap); removeAttribute(attributeForPageMapName(pageMap.getName())); dirty(); } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. *

    * Sets the application that this session is associated with. * * @param application * The application */ public final void setApplication(final Application application) { } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. *

    * Sets the client info object for this session. This will only work when * {@link #getClientInfo()} is not overridden. * * @param clientInfo * the client info object */ public final void setClientInfo(ClientInfo clientInfo) { this.clientInfo = clientInfo; dirty(); } /** * Set the locale for this session. * * @param locale * New locale */ public void setLocale(final Locale locale) { if (locale == null) { throw new IllegalArgumentException("Argument 'locale' must not be null"); } if (!Objects.equal(this.locale, locale)) { dirty(); } this.locale = locale; } /** * Sets the metadata for this session using the given key. If the metadata object is not of the * correct type for the metadata key, an IllegalArgumentException will be thrown. For * information on creating MetaDataKeys, see {@link MetaDataKey}. * * @param key * The singleton key for the metadata * @param object * The metadata object * @throws IllegalArgumentException * @see MetaDataKey */ public final synchronized void setMetaData(final MetaDataKey key, final Serializable object) { metaData = key.set(metaData, object); dirty(); } /** * Set the style (see {@link org.apache.wicket.Session}). * * @param style * The style to set. * @return the Session object */ public final Session setStyle(final String style) { this.style = style; dirty(); return this; } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. *

    * The page will be 'touched' in the session. If it wasn't added yet to the pagemap, it will be * added to the page map else it will set this page to the front. * * If another page was removed because of this it will be cleaned up. * * @param page */ public final void touch(Page page) { // store it in a list, so that the pages are really pushed // to the pagemap when the session does it update/detaches. // all the pages are then detached List lst = touchedPages.get(); if (lst == null) { lst = new ArrayList(); touchedPages.set(lst); lst.add(page); } else if (!lst.contains(page)) { lst.add(page); } } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. *

    * This method will remove a page that was previously added via touch() * * @param page */ public final void untouch(Page page) { List lst = touchedPages.get(); if (lst != null) { lst.remove(page); } } /** * @param visitor * The visitor to call at each Page in this PageMap. */ public final void visitPageMaps(final IPageMapVisitor visitor) { for (final Iterator iterator = getAttributeNames().iterator(); iterator.hasNext();) { final String attribute = iterator.next(); if (attribute.startsWith(pageMapAttributePrefix)) { visitor.pageMap((IPageMap)getAttribute(attribute)); } } } /** * Registers a warning feedback message for this session * * @param message * The feedback message */ public final void warn(final String message) { addFeedbackMessage(message, FeedbackMessage.WARNING); } /** * Adds a feedback message to the list of messages * * @param message * @param level * */ private void addFeedbackMessage(String message, int level) { getFeedbackMessages().add(null, message, level); dirty(); } /** * @param pageMapName * Name of page map * @return Session attribute holding page map */ private static final String attributeForPageMapName(final String pageMapName) { return pageMapAttributePrefix + pageMapName; } /** * Any attach logic for session subclasses. Called when a session is set for the thread. * * @deprecated will not be available in 1.5+ */ @Deprecated protected void attach() { } /** * Any detach logic for session subclasses. This is called on the end of handling a request, * when the RequestCycle is about to be detached from the current thread. */ protected void detach() { if (sessionInvalidated) { invalidateNow(); } } /** * Marks session state as dirty so that it will be flushed at the end of the request. */ public final void dirty() { dirty = true; } /** * Gets the attribute value with the given name * * @param name * The name of the attribute to store * @return The value of the attribute */ protected final Object getAttribute(final String name) { if (!isTemporary()) { RequestCycle cycle = RequestCycle.get(); if (cycle != null) { return getSessionStore().getAttribute(cycle.getRequest(), name); } } else { if (temporarySessionAttributes != null) { return temporarySessionAttributes.get(name); } } return null; } /** * @return List of attributes for this session */ protected final List getAttributeNames() { if (!isTemporary()) { RequestCycle cycle = RequestCycle.get(); if (cycle != null) { return getSessionStore().getAttributeNames(cycle.getRequest()); } } else { if (temporarySessionAttributes != null) { return new ArrayList(temporarySessionAttributes.keySet()); } } return Collections.emptyList(); } /** * Gets the session store. * * @return the session store */ protected ISessionStore getSessionStore() { if (sessionStore == null) { sessionStore = getApplication().getSessionStore(); } return sessionStore; } /** * Removes the attribute with the given name. * * @param name * the name of the attribute to remove */ protected final void removeAttribute(String name) { if (!isTemporary()) { RequestCycle cycle = RequestCycle.get(); if (cycle != null) { getSessionStore().removeAttribute(cycle.getRequest(), name); } } else { if (temporarySessionAttributes != null) { temporarySessionAttributes.remove(name); } } } /** * Adds or replaces the attribute with the given name and value. * * @param name * The name of the attribute * @param value * The value of the attribute */ protected final void setAttribute(String name, Object value) { if (!isTemporary()) { RequestCycle cycle = RequestCycle.get(); if (cycle == null) { throw new IllegalStateException( "Cannot set the attribute: no RequestCycle available. If you get this error when using WicketTester.startPage(Page), make sure to call WicketTester.createRequestCycle() beforehand."); } ISessionStore store = getSessionStore(); Request request = cycle.getRequest(); // extra check on session binding event if (value == this) { Object current = store.getAttribute(request, name); if (current == null) { String id = store.getSessionId(request, false); if (id != null) { // this is a new instance. wherever it came from, bind // the session now store.bind(request, (Session)value); } } } // Set the actual attribute store.setAttribute(request, name, value); } else { // we don't have to synchronize, as it is impossible a temporary // session instance gets shared across threads if (temporarySessionAttributes == null) { temporarySessionAttributes = new HashMap(3); } temporarySessionAttributes.put(name, value); } } /** * NOT TO BE CALLED BY FRAMEWORK USERS. * * @deprecated obsolete method (was meant for internal book keeping really). Clients should * override {@link #detach()} instead. */ @Deprecated protected final void update() { throw new UnsupportedOperationException(); } /** * @param page * The page to add to dirty objects list */ void dirtyPage(final Page page) { List dirtyObjects = getDirtyObjectsList(); if (!dirtyObjects.contains(page)) { dirtyObjects.add(page); } } /** * @param map * The page map to add to dirty objects list */ void dirtyPageMap(final IPageMap map) { // see WICKET-3108 - removed page maps should not be added to dirtyObjects if (!getPageMaps().contains(map)) { return; } synchronized (usedPageMapNames) { usedPageMapNames.remove(map.getName()); usedPageMapNames.addLast(map.getName()); } List dirtyObjects = getDirtyObjectsList(); if (!dirtyObjects.contains(map)) { dirtyObjects.add(map); } } /** * @return The current thread dirty objects list */ List getDirtyObjectsList() { List list = dirtyObjects.get(); if (list == null) { list = new ArrayList(4); dirtyObjects.set(list); } return list; } // TODO remove after deprecation release /** * INTERNAL API. The request cycle when detached will call this. * * FIXME javadoc - does what? */ final void requestDetached() { List touchedPages = Session.touchedPages.get(); Session.touchedPages.set(null); if (touchedPages != null) { for (int i = 0; i < touchedPages.size(); i++) { try { Page page = touchedPages.get(i); // page must be detached before it gets stored page.detach(); page.getPageMap().put(page); } catch (Throwable t) { // catch runtime and errors so that the next page is not detached/serialized and // the pagemap released. (see below) log.error("Exception when detaching/serializing page", t); } dirty = true; } } try { // If state is dirty if (dirty) { // State is no longer dirty dirty = false; // Set attribute. setAttribute(SESSION_ATTRIBUTE_NAME, this); } else { if (log.isDebugEnabled()) { log.debug("update: Session not dirty."); } } List dirtyObjects = Session.dirtyObjects.get(); Session.dirtyObjects.set(null); Map tempMap = new HashMap(); // Go through all dirty entries, replicating any dirty objects if (dirtyObjects != null) { for (final Iterator iterator = dirtyObjects.iterator(); iterator.hasNext();) { String attribute = null; Object object = iterator.next(); if (object instanceof Page) { final Page page = (Page)object; if (page.isPageStateless()) { // check, can it be that stateless pages where added to // the session? // and should be removed now? continue; } attribute = page.getPageMap().attributeForId(page.getNumericId()); if (getAttribute(attribute) == null) { // page removed by another thread. don't add it again. continue; } object = page.getPageMapEntry(); } else if (object instanceof IPageMap) { attribute = attributeForPageMapName(((IPageMap)object).getName()); } // we might override some attributes, so we use a temporary map // and then just copy the last values to real sesssion tempMap.put(attribute, object); } } // in case we have dirty attributes, set them to session if (tempMap.isEmpty() == false) { for (Entry entry : tempMap.entrySet()) { setAttribute(entry.getKey(), entry.getValue()); } } } finally { if (pageMapsUsedInRequest != null) { synchronized (pageMapsUsedInRequest) { Thread t = Thread.currentThread(); Iterator> it = pageMapsUsedInRequest.entrySet() .iterator(); while (it.hasNext()) { Entry entry = it.next(); if ((entry.getValue()).thread == t) { it.remove(); } } pageMapsUsedInRequest.notifyAll(); } } } } /** * * @return the next page id */ synchronized protected int nextPageId() { return pageIdCounter++; } /** * Retrieves the next available session-unique value * * @return session-unique value */ public synchronized int nextSequenceValue() { return sequence++; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy