📄 session.java
字号:
/* * 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.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.Set;import java.util.Map.Entry;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.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). * <ul> * <li><b>Access via RequestCycle </b>- The Session for a {@link RequestCycle} can be retrieved by * calling {@link RequestCycle#getSession()}. * * <li><b>Access via Component </b>- 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()}. * * <li><b>Access via Thread Local </b>- 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. * * <li><b>Locale </b>- 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. * * <li><b>Style </b>- 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. * * <li><b>Resource Loading </b>- 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): * <ul> * 1. [sourcePath]/name[style][locale].[extension] <br> * 2. [sourcePath]/name[locale].[extension] <br> * 3. [sourcePath]/name[style].[extension] <br> * 4. [sourcePath]/name.[extension] <br> * 5. [classPath]/name[style][locale].[extension] <br> * 6. [classPath]/name[locale].[extension] <br> * 7. [classPath]/name[style].[extension] <br> * 8. [classPath]/name.[extension] <br> * </ul> * * <li><b>Session Properties </b>- 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. * * <li><b>Class Resolver </b>- Sessions have a class resolver ( {@link IClassResolver}) * implementation that is used to locate classes for components such as pages. * * <li><b>Page Factory </b>- A pluggable implementation of {@link IPageFactory} is used to * instantiate pages for the session. * * <li><b>Removal </b>- Pages can be removed from the Session forcibly by calling remove(Page) or * removeAll(), although such an action should rarely be necessary. * * <li><b>Flash Messages</b>- 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()); } } /** meta data key for missing body tags logging. */ public static final MetaDataKey PAGEMAP_ACCESS_MDK = new MetaDataKey( PageMapAccessMetaData.class) { 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:"; /** * Checks if the <code>Session</code> 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); } // 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 = (Session)current.get(); if (session == null) { session = findOrCreate(); } return session; } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. * <p> * 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); // execute any attach logic now session.attach(); } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. * <p> * 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. * <p> * 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). * </p> */ private transient Map temporarySessionAttributes; /** A linked list for last used pagemap queue */ private final LinkedList/* <IPageMap> */usedPageMaps = 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 IllegalArgumentException("Parameter 'locale' must not be null"); } } /** * 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 */ 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. * <p> * 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. * </p> * <p> * 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. * </p> */ 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 (Iterator i = temporarySessionAttributes.entrySet().iterator(); i.hasNext();) { Entry entry = (Entry)i.next(); 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-"; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -