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 Enumerationnull
.
+ *
+ * @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 MapisValid
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 Mapnull
.
+ *
+ * @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 EnumerationThe 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