Java tutorial
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ // //// /// /// /// ////// //// // //// //// /// //// //// // //// //// /// //// ///// // /// /// //// ///// // //// ////// // /// ///// // //// //// // //// ///// // //// //// ///////////// //// //// //////////// /// /// ///// ///// //// //// ///// ///// //// ///// // //// //// /// ///// // ///// ///// //////////// //// //// //// //// // The Web framework with class. // ~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2013 Adam R. Nelson // // Licensed 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 com.sector91.wit.http; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /*>>> import checkers.nullness.quals.Nullable;*/ import org.joda.time.DateTime; import org.joda.time.Duration; import org.simpleframework.http.Cookie; import org.simpleframework.http.Request; import org.simpleframework.http.Response; import com.google.common.base.Optional; import com.sector91.wit.WebApp; import com.sector91.wit.data.DataException; import com.sector91.wit.data.NotFoundException; import com.sector91.wit.data.Store; public class Session implements Serializable { private static final long serialVersionUID = 1L; public static final String COOKIE_NAME = "WITSESSIONID"; public static final Duration DEFAULT_LIFETIME = new Duration(15 * 60 * 1000); // 15 minutes private static final Map<Class<? extends WebApp>, Store<Long, Session>> sessionStores = new HashMap<>(); private static final AtomicLong nextId = new AtomicLong(0); private static Store<Long, Session> getStore(Request request) { @SuppressWarnings("unchecked") final Class<? extends WebApp> cls = (Class<? extends WebApp>) request.getAttribute(WebApp.class); if (sessionStores.containsKey(cls)) { return sessionStores.get(cls); } else { final Store<Long, Session> store = new MemorySessionStore(); sessionStores.put(cls, store); return store; } } public static void useSessionStore(WebApp app, Store<Long, Session> store) { sessionStores.put(app.getClass(), store); } public static Session get(Request request) throws NotFoundException { final Cookie sessionCookie = request.getCookie(COOKIE_NAME); if (sessionCookie == null) throw new NotFoundException("Request does not contain a session ID."); final long id; try { id = Long.parseLong(sessionCookie.getValue(), 16); } catch (NumberFormatException ex) { throw new NotFoundException("Request session ID is improperly formatted."); } final Store<Long, Session> store = getStore(request); final Session session; try { session = store.read(id); } catch (NotFoundException ex) { throw ex; } catch (DataException ex) { throw new RuntimeException(ex); // TODO: Handle DataExceptions more intelligently. } session.touch(); if (session.isExpired()) { try { store.delete(id); } catch (NotFoundException ex) { // Do nothing. } catch (DataException ex) { throw new RuntimeException(ex); } throw new NotFoundException("Session has expired."); } return session; } public static Session create(Request request, Response response) { final Store<Long, Session> store = getStore(request); final Session newSession = new Session(nextId.getAndIncrement(), store); try { store.create(newSession); } catch (DataException ex) { throw new RuntimeException(ex); } response.setCookie(COOKIE_NAME, newSession.idString()); return newSession; } public static Session getOrCreate(Request request, Response response) { try { return get(request); } catch (NotFoundException ex) { return create(request, response); } } private final long id; private Duration lifetime; private DateTime expiresAt; private final Map<Class<? extends SessionData<?>>, SessionData<?>> data; private final Store<Long, Session> store; protected Session(long id, Store<Long, Session> store) { this.id = id; this.data = new HashMap<>(); this.lifetime = DEFAULT_LIFETIME; this.expiresAt = DateTime.now().plus(lifetime); this.store = store; } public long id() { return id; } public String idString() { return Long.toHexString(id); } @SuppressWarnings("unchecked") public <D extends SessionData<D>> Optional<D> get(Class<D> dataClass) { return Optional.fromNullable((D) data.get(dataClass).clone()); } public boolean has(Class<? extends SessionData<?>> dataClass) { return data.containsKey(dataClass); } @SuppressWarnings({ "unchecked", "rawtypes" }) public void put(SessionData<?> data) { put((Class) data.getClass(), (SessionData) data); } public <D extends SessionData<D>> void put(Class<D> dataClass, D data) { this.data.put(dataClass, data.clone()); try { store.update(id, this); } catch (DataException ex) { throw new RuntimeException(ex); } } public boolean remove(Class<? extends SessionData<?>> dataClass) { boolean result = this.data.remove(dataClass) != null; try { store.update(id, this); } catch (DataException ex) { throw new RuntimeException(ex); } return result; } public void touch() { this.expiresAt = DateTime.now().plus(lifetime); } public final void expireAfter(long quantity, TimeUnit unit) { expireAfter(new Duration(unit.toMillis(quantity))); try { store.update(id, this); } catch (DataException ex) { throw new RuntimeException(ex); } } public void expireAfter(Duration duration) { lifetime = duration; touch(); } public boolean isExpired() { return DateTime.now().isAfter(expiresAt); } }