001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2017-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.http;
020
021import java.io.Serializable;
022import java.time.Instant;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Optional;
026import java.util.concurrent.ConcurrentHashMap;
027import org.jgrapes.core.Associator;
028import org.jgrapes.http.LanguageSelector.Selection;
029import org.jgrapes.http.events.Request;
030
031/**
032 * Represents a browser session. Session objects are used to store
033 * data related to the browser session. Servers with a small number
034 * of clients usually keep session objects and the associated data
035 * in memory. Servers with a large number of clients may choose to
036 * only keep a LRU cache of session objects in memory and swap
037 * out (persist) other session objects. Therefore the keys and
038 * values stored using a session object must be serializable.
039 * 
040 * Occasionally, data associated with a session object is already
041 * persisted anyway, because its lifetime is beyond that of a session.
042 * To avoid persisting such data twice, the session provides a special
043 * area for "transient data". If components choose to store data
044 * in this area, they must always check before use if the data is
045 * available and recover it if necessary. Using this approach, the
046 * components automatically profit from the LRU caching mechanism
047 * provided by the session manager. As an alternative, components
048 * can use the session {@link #id()} as key to manage the data
049 * on their own.
050 * 
051 * Before removal, the {@link SessionManager} calls the {@link #close}
052 * method. The default implementation iterates through all values
053 * in the transient data map and calls {@link AutoCloseable#close}
054 * for each one that implements the {@link AutoCloseable} interface.
055 * 
056 * Implementations should override {@link Object#hashCode()}
057 * and {@link Object#equals(Object)} in such a way
058 * that the session id is the only relevant attribute (cannot be
059 * done by default methods of the interface).
060 * 
061 * Note that a browser can issue several requests related to the same
062 * session in parallel. Implementations of this interface must 
063 * therefore ensure the thread safety of all methods provided by {@link Map}.
064 * Operations that require consistency across several operations should
065 * synchronize on the session object.
066 */
067public interface Session extends Map<Serializable, Serializable> {
068
069    /**
070     * Obtains a {@link Session} from an {@link Associator}. The
071     * associator must be a {@link Request} (see 
072     * {@link SessionManager#onRequest}) or a web socket channel (see 
073     * {@link SessionManager#onProtocolSwitchAccepted}). As the existence of
074     * an association can safely be assumed in handlers handling
075     * {@link Request}s or handlers handling events on web socket channels,
076     * the method returns {@link Session} and not `Optional<Session>`.
077     * 
078     * The method should
079     * only be used in contexts where an existing association can be assumed.
080     *
081     * @param associator the associator
082     * @return the session
083     */
084    static Session from(Associator associator) {
085        return associator.associatedGet(Session.class).get();
086    }
087
088    /**
089     * Returns the session id.
090     * 
091     * @return the id
092     */
093    @SuppressWarnings("PMD.ShortMethodName")
094    String id();
095
096    /**
097     * Returns the creation time stamp.
098     * 
099     * @return the creation time stamp
100     */
101    Instant createdAt();
102
103    /**
104     * Returns the last used (referenced in request) time stamp. 
105     * 
106     * @return the last used timestamp
107     */
108    Instant lastUsedAt();
109
110    /**
111     * Updates the last used time stamp.
112     */
113    void updateLastUsedAt();
114
115    /**
116     * Return the storage area for transient data. Usually implemented
117     * by a {@link ConcurrentHashMap}. Other implementations must
118     * at least provide the same support for concurrency as 
119     * {@link ConcurrentHashMap}.
120     * 
121     * @return the storage area
122     */
123    Map<Object, Object> transientData();
124
125    /** 
126     * Iterates through all values in the transient data map and 
127     * calls {@link AutoCloseable#close} for each that implements 
128     * the {@link AutoCloseable} interface. Any exceptions are ignored.
129     */
130    @SuppressWarnings({ "PMD.AvoidCatchingGenericException",
131        "PMD.EmptyCatchBlock" })
132    default void close() {
133        for (Object item : transientData().values()) {
134            if (item instanceof AutoCloseable) {
135                try {
136                    ((AutoCloseable) item).close();
137                } catch (Exception e) {
138                    // Ignored
139                }
140            }
141        }
142    }
143
144    /**
145     * Convenience method for retrieving the locale 
146     * set by {@link LanguageSelector} from the session.
147     * 
148     * @return the locale
149     */
150    default Locale locale() {
151        return Optional.ofNullable((Selection) get(Selection.class))
152            .map(selection -> selection.get()[0]).orElse(Locale.getDefault());
153    }
154
155}