抄了apache tomcat的源代码里关于Session, StandardSession, Manager, StandardManager中关于Session管理部分的代码。master
| @@ -0,0 +1,31 @@ | |||||
| /* | |||||
| * 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 me.chanjar.weixin.common.session; | |||||
| /** | |||||
| * Manifest constants for the <code>org.apache.catalina.session</code> | |||||
| * package. | |||||
| * | |||||
| * @author Craig R. McClanahan | |||||
| */ | |||||
| public class Constants { | |||||
| public static final String Package = "me.chanjar.weixin.common.session"; | |||||
| } | |||||
| @@ -0,0 +1,73 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| /** | |||||
| * Created by qianjia on 15/1/21. | |||||
| */ | |||||
| public interface InternalSession { | |||||
| /** | |||||
| * Return the <code>HttpSession</code> for which this object | |||||
| * is the facade. | |||||
| */ | |||||
| WxSession getSession(); | |||||
| /** | |||||
| * Set the <code>isValid</code> flag for this session. | |||||
| * | |||||
| * @param isValid The new value for the <code>isValid</code> flag | |||||
| */ | |||||
| public void setValid(boolean isValid); | |||||
| /** | |||||
| * Return the <code>isValid</code> flag for this session. | |||||
| */ | |||||
| boolean isValid(); | |||||
| /** | |||||
| * Return the session identifier for this session. | |||||
| */ | |||||
| String getIdInternal(); | |||||
| /** | |||||
| * Perform the internal processing required to invalidate this session, | |||||
| * without triggering an exception if the session has already expired. | |||||
| */ | |||||
| void expire(); | |||||
| /** | |||||
| * Update the accessed time information for this session. This method | |||||
| * should be called by the context when a request comes in for a particular | |||||
| * session, even if the application does not reference it. | |||||
| */ | |||||
| void access(); | |||||
| /** | |||||
| * Set the <code>isNew</code> flag for this session. | |||||
| * | |||||
| * @param isNew The new value for the <code>isNew</code> flag | |||||
| */ | |||||
| void setNew(boolean isNew); | |||||
| /** | |||||
| * Set the creation time for this session. This method is called by the | |||||
| * Manager when an existing Session instance is reused. | |||||
| * | |||||
| * @param time The new creation time | |||||
| */ | |||||
| void setCreationTime(long time); | |||||
| /** | |||||
| * Set the default maximum inactive interval (in seconds) | |||||
| * for Sessions created by this Manager. | |||||
| * | |||||
| * @param interval The new default value | |||||
| */ | |||||
| void setMaxInactiveInterval(int interval); | |||||
| /** | |||||
| * Set the session identifier for this session. | |||||
| * | |||||
| * @param id The new session identifier | |||||
| */ | |||||
| void setId(String id); | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| import java.util.Enumeration; | |||||
| /** | |||||
| * Created by qianjia on 15/1/21. | |||||
| */ | |||||
| public class InternalSessionFacade implements WxSession { | |||||
| /** | |||||
| * Wrapped session object. | |||||
| */ | |||||
| private WxSession session = null; | |||||
| public InternalSessionFacade(WxSession session) { | |||||
| session = session; | |||||
| } | |||||
| @Override | |||||
| public Object getAttribute(String name) { | |||||
| return session.getAttribute(name); | |||||
| } | |||||
| @Override | |||||
| public Enumeration<String> getAttributeNames() { | |||||
| return session.getAttributeNames(); | |||||
| } | |||||
| @Override | |||||
| public void setAttribute(String name, Object value) { | |||||
| session.setAttribute(name, value); | |||||
| } | |||||
| @Override | |||||
| public void removeAttribute(String name) { | |||||
| session.removeAttribute(name); | |||||
| } | |||||
| @Override | |||||
| public void invalidate() { | |||||
| session.invalidate(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,66 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| /** | |||||
| * Created by qianjia on 15/1/21. | |||||
| */ | |||||
| public interface InternalSessionManager { | |||||
| /** | |||||
| * Construct and return a new session object, based on the default | |||||
| * settings specified by this Manager's properties. The session | |||||
| * id specified will be used as the session id. | |||||
| * If a new session cannot be created for any reason, return | |||||
| * <code>null</code>. | |||||
| * | |||||
| * @param sessionId The session id which should be used to create the | |||||
| * new session; if <code>null</code>, a new session id will be | |||||
| * generated | |||||
| * @exception IllegalStateException if a new session cannot be | |||||
| * instantiated for any reason | |||||
| */ | |||||
| public InternalSession createSession(String sessionId); | |||||
| /** | |||||
| * Remove this Session from the active Sessions for this Manager. | |||||
| * | |||||
| * @param session Session to be removed | |||||
| */ | |||||
| public void remove(InternalSession session); | |||||
| /** | |||||
| * Remove this Session from the active Sessions for this Manager. | |||||
| * | |||||
| * @param session Session to be removed | |||||
| * @param update Should the expiration statistics be updated | |||||
| */ | |||||
| public void remove(InternalSession session, boolean update); | |||||
| /** | |||||
| * Add this Session to the set of active Sessions for this Manager. | |||||
| * | |||||
| * @param session Session to be added | |||||
| */ | |||||
| void add(InternalSession session); | |||||
| /** | |||||
| * Returns the number of active sessions | |||||
| * | |||||
| * @return number of sessions active | |||||
| */ | |||||
| int getActiveSessions(); | |||||
| /** | |||||
| * Get a session from the recycled ones or create a new empty one. | |||||
| * The PersistentManager manager does not need to create session data | |||||
| * because it reads it from the Store. | |||||
| */ | |||||
| InternalSession createEmptySession(); | |||||
| InternalSession[] findSessions(); | |||||
| /** | |||||
| * Implements the Manager interface, direct call to processExpires | |||||
| */ | |||||
| public void backgroundProcess(); | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| # 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. | |||||
| applicationSession.session.ise=invalid session state | |||||
| applicationSession.value.iae=null value | |||||
| fileStore.saving=Saving Session {0} to file {1} | |||||
| fileStore.loading=Loading Session {0} from file {1} | |||||
| fileStore.removing=Removing Session {0} at file {1} | |||||
| fileStore.deleteFailed=Unable to delete file [{0}] which is preventing the creation of the session storage location | |||||
| fileStore.createFailed=Unable to create directory [{0}] for the storage of session data | |||||
| JDBCStore.close=Exception closing database connection {0} | |||||
| JDBCStore.saving=Saving Session {0} to database {1} | |||||
| JDBCStore.loading=Loading Session {0} from database {1} | |||||
| JDBCStore.removing=Removing Session {0} at database {1} | |||||
| JDBCStore.SQLException=SQL Error {0} | |||||
| serverSession.value.iae=null value | |||||
| sessionManagerImpl.createRandom=Created random number generator for session ID generation in {0}ms. | |||||
| sessionManagerImpl.createSession.tmase=createSession: Too many active sessions | |||||
| sessionManagerImpl.sessionTimeout=Invalid session timeout setting {0} | |||||
| sessionManagerImpl.getSession.ise=getSession: Session id cannot be null | |||||
| sessionManagerImpl.expireException=processsExpire: Exception during session expiration | |||||
| sessionManagerImpl.loading=Loading persisted sessions from {0} | |||||
| sessionManagerImpl.loading.cnfe=ClassNotFoundException while loading persisted sessions: {0} | |||||
| sessionManagerImpl.loading.ioe=IOException while loading persisted sessions: {0} | |||||
| sessionManagerImpl.unloading=Saving persisted sessions to {0} | |||||
| sessionManagerImpl.unloading.debug=Unloading persisted sessions | |||||
| sessionManagerImpl.unloading.ioe=IOException while saving persisted sessions: {0} | |||||
| sessionManagerImpl.unloading.nosessions=No persisted sessions to unload | |||||
| sessionManagerImpl.managerLoad=Exception loading sessions from persistent storage | |||||
| sessionManagerImpl.managerUnload=Exception unloading sessions to persistent storage | |||||
| sessionManagerImpl.createSession.ise=createSession: Session id cannot be null | |||||
| sessionImpl.attributeEvent=Session attribute event listener threw exception | |||||
| sessionImpl.bindingEvent=Session binding event listener threw exception | |||||
| sessionImpl.invalidate.ise=invalidate: Session already invalidated | |||||
| sessionImpl.isNew.ise=isNew: Session already invalidated | |||||
| sessionImpl.getAttribute.ise=getAttribute: Session already invalidated | |||||
| sessionImpl.getAttributeNames.ise=getAttributeNames: Session already invalidated | |||||
| sessionImpl.getCreationTime.ise=getCreationTime: Session already invalidated | |||||
| sessionImpl.getThisAccessedTime.ise=getThisAccessedTime: Session already invalidated | |||||
| sessionImpl.getLastAccessedTime.ise=getLastAccessedTime: Session already invalidated | |||||
| sessionImpl.getId.ise=getId: Session already invalidated | |||||
| sessionImpl.getMaxInactiveInterval.ise=getMaxInactiveInterval: Session already invalidated | |||||
| sessionImpl.getValueNames.ise=getValueNames: Session already invalidated | |||||
| sessionImpl.logoutfail=Exception logging out user when expiring session | |||||
| sessionImpl.notSerializable=Cannot serialize session attribute {0} for session {1} | |||||
| sessionImpl.removeAttribute.ise=removeAttribute: Session already invalidated | |||||
| sessionImpl.sessionEvent=Session event listener threw exception | |||||
| sessionImpl.setAttribute.iae=setAttribute: Non-serializable attribute {0} | |||||
| sessionImpl.setAttribute.ise=setAttribute: Session [{0}] has already been invalidated | |||||
| sessionImpl.setAttribute.namenull=setAttribute: name parameter cannot be null | |||||
| sessionImpl.sessionCreated=Created Session id = {0} | |||||
| persistentManager.loading=Loading {0} persisted sessions | |||||
| persistentManager.unloading=Saving {0} persisted sessions | |||||
| persistentManager.expiring=Expiring {0} sessions before saving them | |||||
| persistentManager.deserializeError=Error deserializing Session {0}: {1} | |||||
| persistentManager.serializeError=Error serializing Session {0}: {1} | |||||
| persistentManager.swapMaxIdle=Swapping session {0} to Store, idle for {1} seconds | |||||
| persistentManager.backupMaxIdle=Backing up session {0} to Store, idle for {1} seconds | |||||
| persistentManager.backupException=Exception occurred when backing up Session {0}: {1} | |||||
| persistentManager.tooManyActive=Too many active sessions, {0}, looking for idle sessions to swap out | |||||
| persistentManager.swapTooManyActive=Swapping out session {0}, idle for {1} seconds too many sessions active | |||||
| persistentManager.processSwaps=Checking for sessions to swap out, {0} active sessions in memory | |||||
| persistentManager.activeSession=Session {0} has been idle for {1} seconds | |||||
| persistentManager.swapIn=Swapping session {0} in from Store | |||||
| persistentManager.swapInException=Exception in the Store during swapIn: {0} | |||||
| persistentManager.swapInInvalid=Swapped session {0} is invalid | |||||
| persistentManager.storeKeysException=Unable to determine the list of session IDs for sessions in the session store, assuming that the store is empty | |||||
| persistentManager.storeSizeException=Unable to determine the number of sessions in the session store, assuming that the store is empty | |||||
| @@ -0,0 +1,294 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| import me.chanjar.weixin.common.util.res.StringManager; | |||||
| import java.util.*; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| /** | |||||
| * Created by qianjia on 15/1/21. | |||||
| */ | |||||
| public class SessionImpl implements WxSession, InternalSession { | |||||
| /** | |||||
| * The string manager for this package. | |||||
| */ | |||||
| protected static final StringManager sm = | |||||
| StringManager.getManager(Constants.Package); | |||||
| // ------------------------------ WxSession | |||||
| protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>(); | |||||
| @Override | |||||
| public Object getAttribute(String name) { | |||||
| if (!isValidInternal()) | |||||
| throw new IllegalStateException | |||||
| (sm.getString("sessionImpl.getAttribute.ise")); | |||||
| if (name == null) return null; | |||||
| return (attributes.get(name)); | |||||
| } | |||||
| @Override | |||||
| public Enumeration<String> getAttributeNames() { | |||||
| if (!isValidInternal()) | |||||
| throw new IllegalStateException | |||||
| (sm.getString("sessionImpl.getAttributeNames.ise")); | |||||
| Set<String> names = new HashSet<String>(); | |||||
| names.addAll(attributes.keySet()); | |||||
| return Collections.enumeration(names); | |||||
| } | |||||
| @Override | |||||
| public void setAttribute(String name, Object value) { | |||||
| // Name cannot be null | |||||
| if (name == null) | |||||
| throw new IllegalArgumentException | |||||
| (sm.getString("sessionImpl.setAttribute.namenull")); | |||||
| // Null value is the same as removeAttribute() | |||||
| if (value == null) { | |||||
| removeAttribute(name); | |||||
| return; | |||||
| } | |||||
| // Validate our current state | |||||
| if (!isValidInternal()) | |||||
| throw new IllegalStateException(sm.getString( | |||||
| "sessionImpl.setAttribute.ise", getIdInternal())); | |||||
| attributes.put(name, value); | |||||
| } | |||||
| @Override | |||||
| public void removeAttribute(String name) { | |||||
| removeAttributeInternal(name); | |||||
| } | |||||
| @Override | |||||
| public void invalidate() { | |||||
| if (!isValidInternal()) | |||||
| throw new IllegalStateException | |||||
| (sm.getString("sessionImpl.invalidate.ise")); | |||||
| // Cause this session to expire | |||||
| expire(); | |||||
| } | |||||
| // ------------------------------ InternalSession | |||||
| /** | |||||
| * The session identifier of this Session. | |||||
| */ | |||||
| protected String id = null; | |||||
| /** | |||||
| * Flag indicating whether this session is valid or not. | |||||
| */ | |||||
| protected volatile boolean isValid = false; | |||||
| /** | |||||
| * Flag indicating whether this session is new or not. | |||||
| */ | |||||
| protected boolean isNew = false; | |||||
| /** | |||||
| * We are currently processing a session expiration, so bypass | |||||
| * certain IllegalStateException tests. NOTE: This value is not | |||||
| * included in the serialized version of this object. | |||||
| */ | |||||
| protected transient volatile boolean expiring = false; | |||||
| /** | |||||
| * The Manager with which this Session is associated. | |||||
| */ | |||||
| protected transient InternalSessionManager manager = null; | |||||
| /** | |||||
| * Type array. | |||||
| */ | |||||
| protected static final String EMPTY_ARRAY[] = new String[0]; | |||||
| /** | |||||
| * The time this session was created, in milliseconds since midnight, | |||||
| * January 1, 1970 GMT. | |||||
| */ | |||||
| protected long creationTime = 0L; | |||||
| /** | |||||
| * The current accessed time for this session. | |||||
| */ | |||||
| protected volatile long thisAccessedTime = creationTime; | |||||
| /** | |||||
| * The last accessed time for this Session. | |||||
| */ | |||||
| protected volatile long lastAccessedTime = creationTime; | |||||
| /** | |||||
| * The default maximum inactive interval for Sessions created by | |||||
| * this Manager. | |||||
| */ | |||||
| protected int maxInactiveInterval = 30 * 60; | |||||
| /** | |||||
| * The facade associated with this session. NOTE: This value is not | |||||
| * included in the serialized version of this object. | |||||
| */ | |||||
| protected transient InternalSessionFacade facade = null; | |||||
| public SessionImpl(InternalSessionManager manager) { | |||||
| this.manager = manager; | |||||
| } | |||||
| @Override | |||||
| public WxSession getSession() { | |||||
| if (facade == null){ | |||||
| facade = new InternalSessionFacade(this); | |||||
| } | |||||
| return (facade); | |||||
| } | |||||
| /** | |||||
| * Return the <code>isValid</code> flag for this session without any expiration | |||||
| * check. | |||||
| */ | |||||
| protected boolean isValidInternal() { | |||||
| return this.isValid; | |||||
| } | |||||
| /** | |||||
| * Set the <code>isValid</code> flag for this session. | |||||
| * | |||||
| * @param isValid The new value for the <code>isValid</code> flag | |||||
| */ | |||||
| @Override | |||||
| public void setValid(boolean isValid) { | |||||
| this.isValid = isValid; | |||||
| } | |||||
| @Override | |||||
| public boolean isValid() { | |||||
| return isValid; | |||||
| } | |||||
| @Override | |||||
| public String getIdInternal() { | |||||
| return (this.id); | |||||
| } | |||||
| protected void removeAttributeInternal(String name) { | |||||
| // Avoid NPE | |||||
| if (name == null) return; | |||||
| // Remove this attribute from our collection | |||||
| attributes.remove(name); | |||||
| } | |||||
| @Override | |||||
| public void expire() { | |||||
| // Check to see if session has already been invalidated. | |||||
| // Do not check expiring at this point as expire should not return until | |||||
| // isValid is false | |||||
| if (!isValid) | |||||
| return; | |||||
| synchronized (this) { | |||||
| // Check again, now we are inside the sync so this code only runs once | |||||
| // Double check locking - isValid needs to be volatile | |||||
| // The check of expiring is to ensure that an infinite loop is not | |||||
| // entered as per bug 56339 | |||||
| if (expiring || !isValid) | |||||
| return; | |||||
| if (manager == null) | |||||
| return; | |||||
| // Mark this session as "being expired" | |||||
| expiring = true; | |||||
| // Remove this session from our manager's active sessions | |||||
| manager.remove(this, true); | |||||
| // We have completed expire of this session | |||||
| setValid(false); | |||||
| expiring = false; | |||||
| // Unbind any objects associated with this session | |||||
| String keys[] = keys(); | |||||
| for (int i = 0; i < keys.length; i++) { | |||||
| removeAttributeInternal(keys[i]); | |||||
| } | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void access() { | |||||
| this.thisAccessedTime = System.currentTimeMillis(); | |||||
| } | |||||
| @Override | |||||
| public void setNew(boolean isNew) { | |||||
| this.isNew = isNew; | |||||
| } | |||||
| @Override | |||||
| public void setCreationTime(long time) { | |||||
| this.creationTime = time; | |||||
| this.lastAccessedTime = time; | |||||
| this.thisAccessedTime = time; | |||||
| } | |||||
| @Override | |||||
| public void setMaxInactiveInterval(int interval) { | |||||
| int oldMaxInactiveInterval = this.maxInactiveInterval; | |||||
| this.maxInactiveInterval = interval; | |||||
| } | |||||
| @Override | |||||
| public void setId(String id) { | |||||
| if ((this.id != null) && (manager != null)) | |||||
| manager.remove(this); | |||||
| this.id = id; | |||||
| if (manager != null) | |||||
| manager.add(this); | |||||
| } | |||||
| /** | |||||
| * Return the names of all currently defined session attributes | |||||
| * as an array of Strings. If there are no defined attributes, a | |||||
| * zero-length array is returned. | |||||
| */ | |||||
| protected String[] keys() { | |||||
| return attributes.keySet().toArray(EMPTY_ARRAY); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,252 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| import me.chanjar.weixin.common.util.res.StringManager; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| public class SessionManagerImpl implements WxSessionManager, InternalSessionManager { | |||||
| protected static final StringManager sm = | |||||
| StringManager.getManager(Constants.Package); | |||||
| /** | |||||
| * The set of currently active Sessions for this Manager, keyed by | |||||
| * session identifier. | |||||
| */ | |||||
| protected Map<String, InternalSession> sessions = new ConcurrentHashMap<String, InternalSession>(); | |||||
| @Override | |||||
| public WxSession getSession(String sessionId) { | |||||
| return getSession(sessionId, true); | |||||
| } | |||||
| @Override | |||||
| public WxSession getSession(String sessionId, boolean create) { | |||||
| if (sessionId == null) { | |||||
| throw new IllegalStateException | |||||
| (sm.getString("sessionManagerImpl.getSession.ise")); | |||||
| } | |||||
| InternalSession session = findSession(sessionId); | |||||
| if ((session != null) && !session.isValid()) { | |||||
| session = null; | |||||
| } | |||||
| if (session != null) { | |||||
| session.access(); | |||||
| return session.getSession(); | |||||
| } | |||||
| // Create a new session if requested and the response is not committed | |||||
| if (!create) { | |||||
| return (null); | |||||
| } | |||||
| session = createSession(sessionId); | |||||
| if (session == null) { | |||||
| return null; | |||||
| } | |||||
| session.access(); | |||||
| return session.getSession(); | |||||
| } | |||||
| // -------------------------------------- InternalSessionManager | |||||
| /** | |||||
| * The descriptive name of this Manager implementation (for logging). | |||||
| */ | |||||
| private static final String name = "SessionManagerImpl"; | |||||
| /** | |||||
| * The maximum number of active Sessions allowed, or -1 for no limit. | |||||
| */ | |||||
| protected int maxActiveSessions = -1; | |||||
| /** | |||||
| * Number of session creations that failed due to maxActiveSessions. | |||||
| */ | |||||
| protected int rejectedSessions = 0; | |||||
| /** | |||||
| * The default maximum inactive interval for Sessions created by | |||||
| * this Manager. | |||||
| */ | |||||
| protected int maxInactiveInterval = 30 * 60; | |||||
| // Number of sessions created by this manager | |||||
| protected long sessionCounter=0; | |||||
| protected volatile int maxActive=0; | |||||
| private final Object maxActiveUpdateLock = new Object(); | |||||
| /** | |||||
| * Processing time during session expiration. | |||||
| */ | |||||
| protected long processingTime = 0; | |||||
| /** | |||||
| * Iteration count for background processing. | |||||
| */ | |||||
| private int count = 0; | |||||
| /** | |||||
| * Frequency of the session expiration, and related manager operations. | |||||
| * Manager operations will be done once for the specified amount of | |||||
| * backgrondProcess calls (ie, the lower the amount, the most often the | |||||
| * checks will occur). | |||||
| */ | |||||
| protected int processExpiresFrequency = 6; | |||||
| @Override | |||||
| public void remove(InternalSession session) { | |||||
| remove(session, false); | |||||
| } | |||||
| @Override | |||||
| public void remove(InternalSession session, boolean update) { | |||||
| if (session.getIdInternal() != null) { | |||||
| sessions.remove(session.getIdInternal()); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Return the active Session, associated with this Manager, with the | |||||
| * specified session id (if any); otherwise return <code>null</code>. | |||||
| * | |||||
| * @param id The session id for the session to be returned | |||||
| * | |||||
| * @exception IllegalStateException if a new session cannot be | |||||
| * instantiated for any reason | |||||
| * @exception java.io.IOException if an input/output error occurs while | |||||
| * processing this request | |||||
| */ | |||||
| protected InternalSession findSession(String id) { | |||||
| if (id == null) | |||||
| return (null); | |||||
| return sessions.get(id); | |||||
| } | |||||
| @Override | |||||
| public InternalSession createSession(String sessionId) { | |||||
| if (sessionId == null) { | |||||
| throw new IllegalStateException | |||||
| (sm.getString("sessionManagerImpl.createSession.ise")); | |||||
| } | |||||
| if ((maxActiveSessions >= 0) && | |||||
| (getActiveSessions() >= maxActiveSessions)) { | |||||
| rejectedSessions++; | |||||
| throw new TooManyActiveSessionsException( | |||||
| sm.getString("sessionManagerImpl.createSession.tmase"), | |||||
| maxActiveSessions); | |||||
| } | |||||
| // Recycle or create a Session instance | |||||
| InternalSession session = createEmptySession(); | |||||
| // Initialize the properties of the new session and return it | |||||
| session.setNew(true); | |||||
| session.setValid(true); | |||||
| session.setCreationTime(System.currentTimeMillis()); | |||||
| session.setMaxInactiveInterval(this.maxInactiveInterval); | |||||
| String id = sessionId; | |||||
| session.setId(id); | |||||
| sessionCounter++; | |||||
| return (session); | |||||
| } | |||||
| @Override | |||||
| public int getActiveSessions() { | |||||
| return sessions.size(); | |||||
| } | |||||
| @Override | |||||
| public InternalSession createEmptySession() { | |||||
| return (getNewSession()); | |||||
| } | |||||
| /** | |||||
| * Get new session class to be used in the doLoad() method. | |||||
| */ | |||||
| protected InternalSession getNewSession() { | |||||
| return new SessionImpl(this); | |||||
| } | |||||
| @Override | |||||
| public void add(InternalSession session) { | |||||
| sessions.put(session.getIdInternal(), session); | |||||
| int size = getActiveSessions(); | |||||
| if( size > maxActive ) { | |||||
| synchronized(maxActiveUpdateLock) { | |||||
| if( size > maxActive ) { | |||||
| maxActive = size; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Return the set of active Sessions associated with this Manager. | |||||
| * If this Manager has no active Sessions, a zero-length array is returned. | |||||
| */ | |||||
| @Override | |||||
| public InternalSession[] findSessions() { | |||||
| return sessions.values().toArray(new InternalSession[0]); | |||||
| } | |||||
| @Override | |||||
| public void backgroundProcess() { | |||||
| count = (count + 1) % processExpiresFrequency; | |||||
| if (count == 0) | |||||
| processExpires(); | |||||
| } | |||||
| /** | |||||
| * Invalidate all sessions that have expired. | |||||
| */ | |||||
| public void processExpires() { | |||||
| long timeNow = System.currentTimeMillis(); | |||||
| InternalSession sessions[] = findSessions(); | |||||
| int expireHere = 0 ; | |||||
| if(log.isDebugEnabled()) | |||||
| log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length); | |||||
| for (int i = 0; i < sessions.length; i++) { | |||||
| if (sessions[i]!=null && !sessions[i].isValid()) { | |||||
| expireHere++; | |||||
| } | |||||
| } | |||||
| long timeEnd = System.currentTimeMillis(); | |||||
| if(log.isDebugEnabled()) | |||||
| log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere); | |||||
| processingTime += ( timeEnd - timeNow ); | |||||
| } | |||||
| /** | |||||
| * Return the descriptive short name of this Manager implementation. | |||||
| */ | |||||
| public String getName() { | |||||
| return (name); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,57 @@ | |||||
| /* | |||||
| * 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 me.chanjar.weixin.common.session; | |||||
| /** | |||||
| * An exception that indicates the maximum number of active sessions has been | |||||
| * reached and the server is refusing to create any new sessions. | |||||
| */ | |||||
| public class TooManyActiveSessionsException | |||||
| extends IllegalStateException | |||||
| { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * The maximum number of active sessions the server will tolerate. | |||||
| */ | |||||
| private final int maxActiveSessions; | |||||
| /** | |||||
| * Creates a new TooManyActiveSessionsException. | |||||
| * | |||||
| * @param message A description for the exception. | |||||
| * @param maxActive The maximum number of active sessions allowed by the | |||||
| * session manager. | |||||
| */ | |||||
| public TooManyActiveSessionsException(String message, | |||||
| int maxActive) | |||||
| { | |||||
| super(message); | |||||
| maxActiveSessions = maxActive; | |||||
| } | |||||
| /** | |||||
| * Gets the maximum number of sessions allowed by the session manager. | |||||
| * | |||||
| * @return The maximum number of sessions allowed by the session manager. | |||||
| */ | |||||
| public int getMaxActiveSessions() | |||||
| { | |||||
| return maxActiveSessions; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| import java.util.Enumeration; | |||||
| /** | |||||
| * Created by qianjia on 15/1/21. | |||||
| */ | |||||
| public interface WxSession { | |||||
| public Object getAttribute(String name); | |||||
| public Enumeration<String> getAttributeNames(); | |||||
| public void setAttribute(String name, Object value); | |||||
| public void removeAttribute(String name); | |||||
| public void invalidate(); | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| package me.chanjar.weixin.common.session; | |||||
| public interface WxSessionManager { | |||||
| public WxSession getSession(String sessionId); | |||||
| public WxSession getSession(String sessionId, boolean create); | |||||
| } | |||||
| @@ -0,0 +1,251 @@ | |||||
| /* | |||||
| * 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 me.chanjar.weixin.common.util.res; | |||||
| import java.text.MessageFormat; | |||||
| import java.util.Enumeration; | |||||
| import java.util.Hashtable; | |||||
| import java.util.LinkedHashMap; | |||||
| import java.util.Locale; | |||||
| import java.util.Map; | |||||
| import java.util.MissingResourceException; | |||||
| import java.util.ResourceBundle; | |||||
| /** | |||||
| * An internationalization / localization helper class which reduces | |||||
| * the bother of handling ResourceBundles and takes care of the | |||||
| * common cases of message formating which otherwise require the | |||||
| * creation of Object arrays and such. | |||||
| * | |||||
| * <p>The StringManager operates on a package basis. One StringManager | |||||
| * per package can be created and accessed via the getManager method | |||||
| * call. | |||||
| * | |||||
| * <p>The StringManager will look for a ResourceBundle named by | |||||
| * the package name given plus the suffix of "LocalStrings". In | |||||
| * practice, this means that the localized information will be contained | |||||
| * in a LocalStrings.properties file located in the package | |||||
| * directory of the classpath. | |||||
| * | |||||
| * <p>Please see the documentation for java.util.ResourceBundle for | |||||
| * more information. | |||||
| * | |||||
| * @author James Duncan Davidson [duncan@eng.sun.com] | |||||
| * @author James Todd [gonzo@eng.sun.com] | |||||
| * @author Mel Martinez [mmartinez@g1440.com] | |||||
| * @see java.util.ResourceBundle | |||||
| */ | |||||
| public class StringManager { | |||||
| private static int LOCALE_CACHE_SIZE = 10; | |||||
| /** | |||||
| * The ResourceBundle for this StringManager. | |||||
| */ | |||||
| private final ResourceBundle bundle; | |||||
| private final Locale locale; | |||||
| /** | |||||
| * Creates a new StringManager for a given package. This is a | |||||
| * private method and all access to it is arbitrated by the | |||||
| * static getManager method call so that only one StringManager | |||||
| * per package will be created. | |||||
| * | |||||
| * @param packageName Name of package to create StringManager for. | |||||
| */ | |||||
| private StringManager(String packageName, Locale locale) { | |||||
| String bundleName = packageName + ".LocalStrings"; | |||||
| ResourceBundle bnd = null; | |||||
| try { | |||||
| bnd = ResourceBundle.getBundle(bundleName, locale); | |||||
| } catch( MissingResourceException ex ) { | |||||
| // Try from the current loader (that's the case for trusted apps) | |||||
| // Should only be required if using a TC5 style classloader structure | |||||
| // where common != shared != server | |||||
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); | |||||
| if( cl != null ) { | |||||
| try { | |||||
| bnd = ResourceBundle.getBundle(bundleName, locale, cl); | |||||
| } catch(MissingResourceException ex2) { | |||||
| // Ignore | |||||
| } | |||||
| } | |||||
| } | |||||
| bundle = bnd; | |||||
| // Get the actual locale, which may be different from the requested one | |||||
| if (bundle != null) { | |||||
| Locale bundleLocale = bundle.getLocale(); | |||||
| if (bundleLocale.equals(Locale.ROOT)) { | |||||
| this.locale = Locale.ENGLISH; | |||||
| } else { | |||||
| this.locale = bundleLocale; | |||||
| } | |||||
| } else { | |||||
| this.locale = null; | |||||
| } | |||||
| } | |||||
| /** | |||||
| Get a string from the underlying resource bundle or return | |||||
| null if the String is not found. | |||||
| @param key to desired resource String | |||||
| @return resource String matching <i>key</i> from underlying | |||||
| bundle or null if not found. | |||||
| @throws IllegalArgumentException if <i>key</i> is null. | |||||
| */ | |||||
| public String getString(String key) { | |||||
| if(key == null){ | |||||
| String msg = "key may not have a null value"; | |||||
| throw new IllegalArgumentException(msg); | |||||
| } | |||||
| String str = null; | |||||
| try { | |||||
| // Avoid NPE if bundle is null and treat it like an MRE | |||||
| if (bundle != null) { | |||||
| str = bundle.getString(key); | |||||
| } | |||||
| } catch(MissingResourceException mre) { | |||||
| //bad: shouldn't mask an exception the following way: | |||||
| // str = "[cannot find message associated with key '" + key + | |||||
| // "' due to " + mre + "]"; | |||||
| // because it hides the fact that the String was missing | |||||
| // from the calling code. | |||||
| //good: could just throw the exception (or wrap it in another) | |||||
| // but that would probably cause much havoc on existing | |||||
| // code. | |||||
| //better: consistent with container pattern to | |||||
| // simply return null. Calling code can then do | |||||
| // a null check. | |||||
| str = null; | |||||
| } | |||||
| return str; | |||||
| } | |||||
| /** | |||||
| * Get a string from the underlying resource bundle and format | |||||
| * it with the given set of arguments. | |||||
| * | |||||
| * @param key | |||||
| * @param args | |||||
| */ | |||||
| public String getString(final String key, final Object... args) { | |||||
| String value = getString(key); | |||||
| if (value == null) { | |||||
| value = key; | |||||
| } | |||||
| MessageFormat mf = new MessageFormat(value); | |||||
| mf.setLocale(locale); | |||||
| return mf.format(args, new StringBuffer(), null).toString(); | |||||
| } | |||||
| /** | |||||
| * Identify the Locale this StringManager is associated with | |||||
| */ | |||||
| public Locale getLocale() { | |||||
| return locale; | |||||
| } | |||||
| // -------------------------------------------------------------- | |||||
| // STATIC SUPPORT METHODS | |||||
| // -------------------------------------------------------------- | |||||
| private static final Map<String, Map<Locale,StringManager>> managers = | |||||
| new Hashtable<String, Map<Locale,StringManager>>(); | |||||
| /** | |||||
| * Get the StringManager for a particular package. If a manager for | |||||
| * a package already exists, it will be reused, else a new | |||||
| * StringManager will be created and returned. | |||||
| * | |||||
| * @param packageName The package name | |||||
| */ | |||||
| public static final synchronized StringManager getManager( | |||||
| String packageName) { | |||||
| return getManager(packageName, Locale.getDefault()); | |||||
| } | |||||
| /** | |||||
| * Get the StringManager for a particular package and Locale. If a manager | |||||
| * for a package/Locale combination already exists, it will be reused, else | |||||
| * a new StringManager will be created and returned. | |||||
| * | |||||
| * @param packageName The package name | |||||
| * @param locale The Locale | |||||
| */ | |||||
| public static final synchronized StringManager getManager( | |||||
| String packageName, Locale locale) { | |||||
| Map<Locale,StringManager> map = managers.get(packageName); | |||||
| if (map == null) { | |||||
| /* | |||||
| * Don't want the HashMap to be expanded beyond LOCALE_CACHE_SIZE. | |||||
| * Expansion occurs when size() exceeds capacity. Therefore keep | |||||
| * size at or below capacity. | |||||
| * removeEldestEntry() executes after insertion therefore the test | |||||
| * for removal needs to use one less than the maximum desired size | |||||
| * | |||||
| */ | |||||
| map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @Override | |||||
| protected boolean removeEldestEntry( | |||||
| Map.Entry<Locale,StringManager> eldest) { | |||||
| if (size() > (LOCALE_CACHE_SIZE - 1)) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| managers.put(packageName, map); | |||||
| } | |||||
| StringManager mgr = map.get(locale); | |||||
| if (mgr == null) { | |||||
| mgr = new StringManager(packageName, locale); | |||||
| map.put(locale, mgr); | |||||
| } | |||||
| return mgr; | |||||
| } | |||||
| /** | |||||
| * Retrieve the StringManager for a list of Locales. The first StringManager | |||||
| * found will be returned. | |||||
| * | |||||
| * @param requestedLocales the list of Locales | |||||
| * | |||||
| * @return the found StringManager or the default StringManager | |||||
| */ | |||||
| public static StringManager getManager(String packageName, | |||||
| Enumeration<Locale> requestedLocales) { | |||||
| while (requestedLocales.hasMoreElements()) { | |||||
| Locale locale = requestedLocales.nextElement(); | |||||
| StringManager result = getManager(packageName, locale); | |||||
| if (result.getLocale().equals(locale)) { | |||||
| return result; | |||||
| } | |||||
| } | |||||
| // Return the default | |||||
| return getManager(packageName); | |||||
| } | |||||
| } | |||||