diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java new file mode 100644 index 000000000..33518f176 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java @@ -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 org.apache.catalina.session + * package. + * + * @author Craig R. McClanahan + */ + +public class Constants { + + public static final String Package = "me.chanjar.weixin.common.session"; + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java new file mode 100644 index 000000000..b13f5c86b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.common.session; + +/** + * Created by qianjia on 15/1/21. + */ +public interface InternalSession { + + /** + * Return the HttpSession for which this object + * is the facade. + */ + WxSession getSession(); + + /** + * Set the isValid flag for this session. + * + * @param isValid The new value for the isValid flag + */ + public void setValid(boolean isValid); + + /** + * Return the isValid 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 isNew flag for this session. + * + * @param isNew The new value for the isNew 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); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java new file mode 100644 index 000000000..242b4c6f6 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java @@ -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 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(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java new file mode 100644 index 000000000..e29fbd1d2 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java @@ -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 + * null. + * + * @param sessionId The session id which should be used to create the + * new session; if null, 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(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties new file mode 100644 index 000000000..ede528f73 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties @@ -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 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java new file mode 100644 index 000000000..eabe399ec --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java @@ -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 attributes = new ConcurrentHashMap(); + + @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 getAttributeNames() { + if (!isValidInternal()) + throw new IllegalStateException + (sm.getString("sessionImpl.getAttributeNames.ise")); + + Set names = new HashSet(); + 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 isValid flag for this session without any expiration + * check. + */ + protected boolean isValidInternal() { + return this.isValid; + } + + /** + * Set the isValid flag for this session. + * + * @param isValid The new value for the isValid 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); + + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java new file mode 100644 index 000000000..464296cc9 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java @@ -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 sessions = new ConcurrentHashMap(); + + @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 null. + * + * @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); + + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java new file mode 100644 index 000000000..bd0c0f2ba --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java @@ -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; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java new file mode 100644 index 000000000..70d81aff5 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java @@ -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 getAttributeNames(); + + public void setAttribute(String name, Object value); + + public void removeAttribute(String name); + + public void invalidate(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java new file mode 100644 index 000000000..e2b1dd79b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java @@ -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); + + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java new file mode 100644 index 000000000..4668ec25d --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java @@ -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. + * + *

The StringManager operates on a package basis. One StringManager + * per package can be created and accessed via the getManager method + * call. + * + *

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. + * + *

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 key from underlying + bundle or null if not found. + @throws IllegalArgumentException if key 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> managers = + new Hashtable>(); + + /** + * 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 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_CACHE_SIZE, 1, true) { + private static final long serialVersionUID = 1L; + @Override + protected boolean removeEldestEntry( + Map.Entry 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 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); + } +}