| /* |
| * 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.time.Duration; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.function.Supplier; |
| import org.apache.wicket.application.IClassResolver; |
| import org.apache.wicket.authorization.IAuthorizationStrategy; |
| import org.apache.wicket.core.request.ClientInfo; |
| import org.apache.wicket.core.util.lang.WicketObjects; |
| import org.apache.wicket.event.IEvent; |
| import org.apache.wicket.event.IEventSink; |
| import org.apache.wicket.feedback.FeedbackMessage; |
| import org.apache.wicket.feedback.FeedbackMessages; |
| import org.apache.wicket.feedback.IFeedbackContributor; |
| import org.apache.wicket.page.IPageManager; |
| import org.apache.wicket.page.PageAccessSynchronizer; |
| import org.apache.wicket.request.Request; |
| import org.apache.wicket.request.cycle.RequestCycle; |
| import org.apache.wicket.session.ISessionStore; |
| import org.apache.wicket.util.LazyInitializer; |
| import org.apache.wicket.util.io.IClusterable; |
| import org.apache.wicket.util.lang.Args; |
| import org.apache.wicket.util.lang.Objects; |
| 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</b> - the Session can be retrieved either by {@link Component#getSession()} |
| * or by directly calling the static method Session.get(). All classes which extend directly or indirectly |
| * {@link org.apache.wicket.markup.html.WebMarkupContainer} can also use its convenience method |
| * {@link org.apache.wicket.markup.html.WebMarkupContainer#getWebSession()}</li> |
| * |
| * <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> |
| * |
| * <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> |
| * |
| * <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): |
| * <ol> |
| * <li> [sourcePath]/name[style][locale].[extension]</li> |
| * <li> [sourcePath]/name[locale].[extension]</li> |
| * <li> [sourcePath]/name[style].[extension]</li> |
| * <li> [sourcePath]/name.[extension]</li> |
| * <li> [classPath]/name[style][locale].[extension]</li> |
| * <li> [classPath]/name[locale].[extension]</li> |
| * <li> [classPath]/name[style].[extension]</li> |
| * <li> [classPath]/name.[extension]</li> |
| * </ol> |
| * </li> |
| * |
| * <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> |
| * |
| * <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> |
| * |
| * <li><b>Page Factory</b> - A pluggable implementation of {@link IPageFactory} is used to |
| * instantiate pages for the session.</li> |
| * |
| * <li><b>Removal</b> - Pages can be removed from the Session forcibly by calling clear(), |
| * although such an action should rarely be necessary.</li> |
| * |
| * <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.</li> |
| * </ul> |
| * |
| * @author Jonathan Locke |
| * @author Eelco Hillenius |
| * @author Igor Vaynberg (ivaynberg) |
| */ |
| public abstract class Session implements IClusterable, IEventSink, IMetadataContext<Serializable, Session>, IFeedbackContributor |
| { |
| private static final long serialVersionUID = 1L; |
| |
| /** Logging object */ |
| private static final Logger log = LoggerFactory.getLogger(Session.class); |
| |
| /** records if pages have been unlocked for the current request */ |
| private static final MetaDataKey<Boolean> PAGES_UNLOCKED = new MetaDataKey<>() |
| { |
| private static final long serialVersionUID = 1L; |
| }; |
| |
| /** records if session has been invalidated by the current request */ |
| private static final MetaDataKey<Boolean> SESSION_INVALIDATED = 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"; |
| |
| /** a sequence used for whenever something session-specific needs a unique value */ |
| private final AtomicInteger sequence = new AtomicInteger(1); |
| |
| /** a sequence used for generating page IDs */ |
| private final AtomicInteger pageId = new AtomicInteger(0); |
| |
| /** synchronize page's access by session */ |
| private final Supplier<PageAccessSynchronizer> pageAccessSynchronizer; |
| |
| /** |
| * Checks existence of a <code>Session</code> associated with the current thread. |
| * |
| * @return {@code true} if {@link Session#get()} can return the instance of session, |
| * {@code false} otherwise |
| */ |
| public static boolean exists() |
| { |
| Session session = ThreadContext.getSession(); |
| |
| if (session == null) |
| { |
| // no session is available via ThreadContext, so lookup in session store |
| RequestCycle requestCycle = RequestCycle.get(); |
| if (requestCycle != null) |
| { |
| session = Application.get().getSessionStore().lookup(requestCycle.getRequest()); |
| if (session != null) |
| { |
| ThreadContext.setSession(session); |
| } |
| } |
| } |
| return session != null; |
| } |
| |
| /** |
| * Returns session associated to current thread. Always returns a session during a request |
| * cycle, even though the session might be temporary |
| * |
| * @return session. |
| */ |
| public static Session get() |
| { |
| Session session = ThreadContext.getSession(); |
| if (session != null) |
| { |
| return session; |
| } |
| else |
| { |
| return Application.get().fetchCreateAndSetSession(RequestCycle.get()); |
| } |
| } |
| |
| /** |
| * Cached instance of agent info which is typically designated by calling |
| * {@link Session#getClientInfo()}. |
| */ |
| protected ClientInfo clientInfo; |
| |
| /** True if session state has been changed */ |
| private transient volatile boolean dirty = false; |
| |
| /** feedback messages */ |
| private final FeedbackMessages feedbackMessages = new FeedbackMessages(); |
| |
| /** cached id because you can't access the id after session unbound */ |
| private volatile String id = null; |
| |
| /** The locale to use when loading resources for this session. */ |
| private final AtomicReference<Locale> locale; |
| |
| /** Session level meta data. */ |
| private MetaDataEntry<?>[] metaData; |
| |
| /** |
| * 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 final AtomicReference<String> style = new AtomicReference<>(); |
| |
| /** |
| * 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<String, Serializable> temporarySessionAttributes; |
| |
| /** |
| * Constructor. Note that {@link RequestCycle} is not available until this constructor returns. |
| * |
| * @param request |
| * The current request |
| */ |
| public Session(Request request) |
| { |
| Locale locale = request.getLocale(); |
| if (locale == null) |
| { |
| throw new IllegalStateException( |
| "Request#getLocale() cannot return null, request has to have a locale set on it"); |
| } |
| this.locale = new AtomicReference<>(locale); |
| |
| pageAccessSynchronizer = new PageAccessSynchronizerProvider(); |
| } |
| |
| /** |
| * 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 (Map.Entry<String, Serializable> entry : temporarySessionAttributes.entrySet()) |
| { |
| store.setAttribute(request, entry.getKey(), entry.getValue()); |
| } |
| temporarySessionAttributes = null; |
| } |
| } |
| } |
| |
| /** |
| * Removes all pages from the session. Although this method should rarely be needed, it is |
| * available (possibly for security reasons). |
| */ |
| public final void clear() |
| { |
| if (isTemporary() == false) |
| { |
| getPageManager().clear(); |
| } |
| } |
| |
| /** |
| * Registers an error feedback message for this session |
| * |
| * @param message |
| * The feedback message |
| */ |
| @Override |
| public final void error(final Serializable message) |
| { |
| addFeedbackMessage(message, FeedbackMessage.ERROR); |
| } |
| |
| /** |
| * Registers an fatal feedback message for this session |
| * |
| * @param message |
| * The feedback message |
| */ |
| @Override |
| public final void fatal(final Serializable message) |
| { |
| addFeedbackMessage(message, FeedbackMessage.FATAL); |
| } |
| |
| /** |
| * Registers an debug feedback message for this session |
| * |
| * @param message |
| * The feedback message |
| */ |
| @Override |
| public final void debug(final Serializable message) |
| { |
| addFeedbackMessage(message, FeedbackMessage.DEBUG); |
| } |
| |
| /** |
| * 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. |
| * |
| * @return the client info object based on this request |
| */ |
| public abstract ClientInfo getClientInfo(); |
| |
| /** |
| * 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 |
| * <code>null</code> 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) |
| { |
| updateId(); |
| |
| // we have one? |
| if (id != null) |
| { |
| dirty(); |
| } |
| } |
| return id; |
| } |
| |
| private void updateId() |
| { |
| RequestCycle requestCycle = RequestCycle.get(); |
| if (requestCycle != null) |
| { |
| id = getSessionStore().getSessionId(requestCycle.getRequest(), false); |
| } |
| } |
| |
| /** |
| * Get this session's locale. |
| * |
| * @return This session's locale |
| */ |
| public Locale getLocale() |
| { |
| return locale.get(); |
| } |
| |
| /** |
| * Gets metadata for this session using the given key. |
| * |
| * @param key |
| * The key for the data |
| * @param <M> |
| * The type of the metadata. |
| * @return The metadata |
| * @see MetaDataKey |
| */ |
| @Override |
| public synchronized final <M extends Serializable> M getMetaData(final MetaDataKey<M> key) |
| { |
| return key.get(metaData); |
| } |
| |
| /** |
| * @return The page factory for this session |
| */ |
| public IPageFactory getPageFactory() |
| { |
| return getApplication().getPageFactory(); |
| } |
| |
| /** |
| * @return Size of this session |
| */ |
| public final long getSizeInBytes() |
| { |
| return WicketObjects.sizeof(this); |
| } |
| |
| /** |
| * 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.get(); |
| } |
| |
| /** |
| * Registers an informational feedback message for this session |
| * |
| * @param message |
| * The feedback message |
| */ |
| @Override |
| public final void info(final Serializable message) |
| { |
| addFeedbackMessage(message, FeedbackMessage.INFO); |
| } |
| |
| /** |
| * Registers an success feedback message for this session |
| * |
| * @param message |
| * The feedback message |
| */ |
| @Override |
| public final void success(final Serializable message) |
| { |
| addFeedbackMessage(message, FeedbackMessage.SUCCESS); |
| } |
| |
| /** |
| * 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() |
| { |
| RequestCycle.get().setMetaData(SESSION_INVALIDATED, true); |
| } |
| |
| /** |
| * Invalidate and remove session store and page manager |
| */ |
| private void destroy() |
| { |
| if (getSessionStore() != null) |
| { |
| sessionStore.invalidate(RequestCycle.get().getRequest()); |
| sessionStore = null; |
| id = null; |
| RequestCycle.get().setMetaData(SESSION_INVALIDATED, false); |
| clientInfo = null; |
| dirty = false; |
| metaData = null; |
| } |
| } |
| |
| /** |
| * 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() |
| { |
| if (isSessionInvalidated() == false) |
| { |
| invalidate(); |
| } |
| |
| // clear all pages possibly pending in the request |
| getPageManager().clear(); |
| |
| destroy(); |
| feedbackMessages.clear(); |
| setStyle(null); |
| pageId.set(0); |
| sequence.set(0); |
| temporarySessionAttributes = null; |
| } |
| |
| /** |
| * Replaces the underlying (Web)Session, invalidating the current one and creating a new one. By |
| * calling {@link ISessionStore#invalidate(Request)} and {@link #bind()} |
| * |
| * If you are looking for a mean against session fixation attack, consider to use {@link #changeSessionId()}. |
| */ |
| public void replaceSession() |
| { |
| destroy(); |
| 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 Boolean.TRUE.equals(RequestCycle.get().getMetaData(SESSION_INVALIDATED)); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. |
| * <p> |
| * 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 Session setClientInfo(ClientInfo clientInfo) |
| { |
| this.clientInfo = clientInfo; |
| dirty(); |
| return this; |
| } |
| |
| /** |
| * Set the locale for this session. |
| * |
| * @param locale |
| * New locale |
| */ |
| public Session setLocale(final Locale locale) |
| { |
| Args.notNull(locale, "locale"); |
| |
| if (!Objects.equal(getLocale(), locale)) |
| { |
| this.locale.set(locale); |
| dirty(); |
| } |
| return this; |
| } |
| |
| /** |
| * 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 |
| */ |
| @Override |
| public final synchronized <M extends Serializable> Session setMetaData(final MetaDataKey<M> key, final M object) |
| { |
| metaData = key.set(metaData, object); |
| dirty(); |
| return this; |
| } |
| |
| /** |
| * 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) |
| { |
| if (!Objects.equal(getStyle(), style)) |
| { |
| this.style.set(style); |
| dirty(); |
| } |
| return this; |
| } |
| |
| /** |
| * Registers a warning feedback message for this session |
| * |
| * @param message |
| * The feedback message |
| */ |
| @Override |
| public final void warn(final Serializable message) |
| { |
| addFeedbackMessage(message, FeedbackMessage.WARNING); |
| } |
| |
| /** |
| * Adds a feedback message to the list of messages |
| * |
| * @param message |
| * @param level |
| * |
| */ |
| private void addFeedbackMessage(Serializable message, int level) |
| { |
| getFeedbackMessages().add(null, message, level); |
| dirty(); |
| } |
| |
| /** |
| * End the current request. |
| */ |
| public void endRequest() { |
| if (isSessionInvalidated()) |
| { |
| invalidateNow(); |
| } |
| else if (!isTemporary()) |
| { |
| // WICKET-5103 container might have changed id |
| updateId(); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| public void detach() |
| { |
| detachFeedback(); |
| |
| pageAccessSynchronizer.get().unlockAllPages(); |
| RequestCycle.get().setMetaData(PAGES_UNLOCKED, true); |
| } |
| |
| private void detachFeedback() |
| { |
| final int removed = feedbackMessages.clear(getApplication().getApplicationSettings() |
| .getFeedbackMessageCleanupFilter()); |
| |
| if (removed != 0) |
| { |
| dirty(); |
| } |
| |
| feedbackMessages.detach(); |
| } |
| |
| /** |
| * NOT PART OF PUBLIC API, DO NOT CALL |
| * |
| * Detaches internal state of {@link Session} |
| */ |
| public void internalDetach() |
| { |
| if (dirty) |
| { |
| Request request = RequestCycle.get().getRequest(); |
| getSessionStore().flushSession(request, this); |
| } |
| dirty = false; |
| } |
| |
| /** |
| * Marks session state as dirty so that it will be (re)stored in the ISessionStore |
| * at the end of the request. |
| * <strong>Note</strong>: binds the session if it is temporary |
| */ |
| public final void dirty() |
| { |
| dirty(true); |
| } |
| |
| /** |
| * Marks session state as dirty so that it will be re-stored in the ISessionStore |
| * at the end of the request. |
| * |
| * @param forced |
| * A flag indicating whether the session should be marked as dirty even |
| * when it is temporary. If {@code true} the Session will be bound. |
| */ |
| public final void dirty(boolean forced) |
| { |
| if (isTemporary()) |
| { |
| if (forced) |
| { |
| dirty = true; |
| } |
| } |
| else |
| { |
| 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 |
| */ |
| public final Serializable 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 |
| */ |
| public final List<String> getAttributeNames() |
| { |
| if (!isTemporary()) |
| { |
| RequestCycle cycle = RequestCycle.get(); |
| if (cycle != null) |
| { |
| return Collections.unmodifiableList(getSessionStore().getAttributeNames( |
| cycle.getRequest())); |
| } |
| } |
| else |
| { |
| if (temporarySessionAttributes != null) |
| { |
| return Collections.unmodifiableList(new ArrayList<String>( |
| 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 |
| */ |
| public 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 |
| */ |
| public final Session setAttribute(String name, Serializable 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); |
| } |
| return this; |
| } |
| |
| /** |
| * Retrieves the next available session-unique value |
| * |
| * @return session-unique value |
| */ |
| public int nextSequenceValue() |
| { |
| dirty(false); |
| return sequence.getAndIncrement(); |
| } |
| |
| /** |
| * |
| * @return the next page id |
| */ |
| public int nextPageId() |
| { |
| dirty(false); |
| return pageId.getAndIncrement(); |
| } |
| |
| /** |
| * Returns the {@link IPageManager} instance. |
| * |
| * @return {@link IPageManager} instance. |
| */ |
| public final IPageManager getPageManager() |
| { |
| if (Boolean.TRUE.equals(RequestCycle.get().getMetaData(PAGES_UNLOCKED))) { |
| throw new WicketRuntimeException("The request has been processed. Access to pages is no longer allowed"); |
| } |
| |
| IPageManager manager = Application.get().internalGetPageManager(); |
| return pageAccessSynchronizer.get().adapt(manager); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void onEvent(IEvent<?> event) |
| { |
| } |
| |
| /** |
| * A callback method that is executed when the user session is invalidated |
| * either by explicit call to {@link org.apache.wicket.Session#invalidate()} |
| * or due to HttpSession expiration. |
| * |
| * <p>In case of session expiration this method is called in a non-worker thread, i.e. |
| * there are no thread locals exported for the Application, RequestCycle and Session. |
| * The Session is the current instance. The Application can be found by using |
| * {@link Application#get(String)}. There is no way to get a reference to a RequestCycle</p> |
| */ |
| public void onInvalidate() |
| { |
| } |
| |
| /** |
| * Change the id of the underlying (Web)Session if this last one is permanent. |
| * <p> |
| * Call upon login to protect against session fixation. |
| * |
| * @see "http://www.owasp.org/index.php/Session_Fixation" |
| */ |
| public void changeSessionId() |
| { |
| if (isTemporary()) |
| { |
| return; |
| } |
| |
| id = generateNewSessionId(); |
| } |
| |
| /** |
| * Change the id of the underlying (Web)Session. |
| * |
| * @return the new session id value. |
| */ |
| protected abstract String generateNewSessionId(); |
| |
| /** |
| * Factory method for PageAccessSynchronizer instances |
| * |
| * @param timeout |
| * The configured timeout. See {@link org.apache.wicket.settings.RequestCycleSettings#getTimeout()} |
| * @return A new instance of PageAccessSynchronizer |
| */ |
| protected PageAccessSynchronizer newPageAccessSynchronizer(Duration timeout) |
| { |
| return new PageAccessSynchronizer(timeout); |
| } |
| |
| private final class PageAccessSynchronizerProvider extends LazyInitializer<PageAccessSynchronizer> |
| { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected PageAccessSynchronizer createInstance() |
| { |
| final Duration timeout; |
| if (Application.exists()) |
| { |
| timeout = Application.get().getRequestCycleSettings().getTimeout(); |
| } |
| else |
| { |
| timeout = Duration.ofMinutes(1); |
| log.warn( |
| "PageAccessSynchronizer created outside of application thread, using default timeout: {}", |
| timeout); |
| } |
| return newPageAccessSynchronizer(timeout); |
| } |
| } |
| |
| } |