/** * Get a new session-id from the distributed store * * @return new session-id */ protected String getNextId() { return sessionIDGenerator_.getSessionId(); }
/** * Base abstract implementation of Tomcat manager without the concept of session operations, e.g., * add, remove, etc. * * @author Ben Wang * @author Hany Mesha * @author Brian Stansberry * @version $Revision: 91181 $ */ public abstract class JBossManager implements AbstractJBossManager, Lifecycle, JBossManagerMBean, PropertyChangeListener { // ------------------------------------------------------------------ Fields protected ReplicationStatistics stats_ = new ReplicationStatistics(); /** * Session passivation flag set in jboss-web.xml by the user. If true, then the session * passivation is enabled for this web application, otherwise, it's disabled */ protected boolean passivationMode_ = false; /** * Min time (milliseconds) the session must be idle since lastAccesstime before it's eligible for * passivation if passivation is enabled and more than maxActiveAllowed_ sessions are in memory. * Setting to -1 means it's ignored. */ protected long passivationMinIdleTime_ = -1; /** * Max time (milliseconds) the session must be idle since lastAccesstime before it will be * passivated if passivation is enabled. Setting to -1 means session should not be forced out. */ protected long passivationMaxIdleTime_ = -1; /** The lifecycle_ event support for this component. */ protected LifecycleSupport lifecycle_ = new LifecycleSupport(this); /** Has this component been started_ yet? */ protected boolean started_ = false; /** * Are we allowing backgroundProcess() to execute? We use an object so stop() can lock on it to * wait for */ protected AtomicBoolean backgroundProcessAllowed = new AtomicBoolean(); /** The objectname this Manager is associated with */ protected ObjectName objectName_; /** The Log object for this class */ protected Logger log_ = Logger.getLogger(this.getClass().getName()); /** * Whether trace logging is enabled for our logger. Rechecked every time backgroundProcess() is * invoked. */ protected boolean trace_ = log_.isTraceEnabled(); /** The Container with which this Manager is associated. */ protected Container container_; /** * /** The distributable flag for Sessions created by this Manager. If this flag is set to <code> * true</code>, any user attributes added to a session controlled by this Manager must be * Serializable. */ protected boolean distributable_ = true; /** The default maximum inactive interval for Sessions created by this Manager. */ protected int maxInactiveInterval_ = 60; /** Maximum of active sessions allowed. -1 is unlimited. */ protected int maxActiveAllowed_ = -1; /** Number of sessions created by this manager */ protected AtomicInteger createdCounter_ = new AtomicInteger(); /** number of sessions rejected because the number active sessions exceeds maxActive */ protected AtomicInteger rejectedCounter_ = new AtomicInteger(); /** Number of active sessions */ protected AtomicInteger localActiveCounter_ = new AtomicInteger(); /** Maximum number of concurrently locally active sessions */ protected AtomicInteger maxLocalActiveCounter_ = new AtomicInteger(); /** Maximum number of active sessions seen so far */ protected AtomicInteger maxActiveCounter_ = new AtomicInteger(); /** Number of sessions that have been active locally that are now expired. */ protected AtomicInteger expiredCounter_ = new AtomicInteger(); /** Number of ms since last call to reset() */ protected long timeSinceLastReset_ = 0; /** Cumulative time spent in backgroundProcess */ protected AtomicLong processingTime_ = new AtomicLong(); /** Stores the locally active sessions. */ protected final Map<String, ClusteredSession<? extends OutgoingDistributableSessionData>> sessions_ = new ConcurrentHashMap< String, ClusteredSession<? extends OutgoingDistributableSessionData>>(); /** The property change support for this component. */ protected PropertyChangeSupport support_ = new PropertyChangeSupport(this); /** Generates ids for new sessions */ protected SessionIDGenerator sessionIDGenerator_ = SessionIDGenerator.getInstance();; /** Our containing engine's jvmRoute (if it has one) */ protected String jvmRoute_; /** Our JMX Server */ protected MBeanServer mserver_ = null; /** How often calls to backgroundProcess() should trigger expiration/passivation processing */ protected volatile int processExpiresFrequency = 1; /** * How many times backgroundProcess() has been called since we last processed * expiration/passivation. */ protected int backgroundProcessCount = 0; /** Maximum time in ms a now expired session has been alive */ protected AtomicInteger maxAliveTime = new AtomicInteger(); /** Average time in ms a now expired session has been alive */ protected AtomicInteger averageAliveTime = new AtomicInteger(); /** * Number of times our session id generator has generated an id that matches an existing session. */ protected AtomicInteger duplicates_ = new AtomicInteger(); // TODO Need a string manager to handle localization // ------------------------------------------------------------ Constructors /** Creates a new JBossManager */ protected JBossManager() {} // -------------------------------------------------------------- Properties public boolean getUseSessionPassivation() { return passivationMode_; } public void setUseSessionPassivation(boolean enabled) { this.passivationMode_ = enabled; } public long getPassivationMinIdleTime() { return passivationMinIdleTime_; } public void setPassivationMinIdleTime(long passivationMinIdleTime) { this.passivationMinIdleTime_ = passivationMinIdleTime; } public long getPassivationMaxIdleTime() { return passivationMaxIdleTime_; } public void setPassivationMaxIdleTime(long passivationMaxIdleTime) { this.passivationMaxIdleTime_ = passivationMaxIdleTime; } // ---------------------------------------------------- AbstractJBossManager /** {@inheritDoc} */ public void init(String name, JBossWebMetaData webMetaData) throws ClusteringNotSupportedException { if (webMetaData.getMaxActiveSessions() != null) { maxActiveAllowed_ = webMetaData.getMaxActiveSessions().intValue(); } PassivationConfig pConfig = webMetaData.getPassivationConfig(); if (pConfig != null) { if (pConfig.getUseSessionPassivation() != null) { setUseSessionPassivation(pConfig.getUseSessionPassivation().booleanValue()); if (getUseSessionPassivation()) { Integer min = pConfig.getPassivationMinIdleTime(); if (min != null) setPassivationMinIdleTime(min.intValue()); Integer max = pConfig.getPassivationMaxIdleTime(); if (max != null) setPassivationMaxIdleTime(max.intValue()); } } } log_.debug( "init(): maxActiveSessions allowed is " + maxActiveAllowed_ + " and passivationMode is " + passivationMode_); } /** {@inheritDoc} */ public String getJvmRoute() { if (jvmRoute_ == null) { Engine e = getEngine(); jvmRoute_ = (e == null ? null : e.getJvmRoute()); } return jvmRoute_; } /** {@inheritDoc} */ public void setNewSessionCookie(String sessionId, HttpServletResponse response) { if (response != null) { Context context = (Context) container_; Connector connector = ((Response) response).getConnector(); if (context.getCookies()) { // set a new session cookie TomcatCookie cookie = new TomcatCookie(Globals.SESSION_COOKIE_NAME, sessionId); // JBAS-6206. Configure cookie a la o.a.c.connector.Request.configureSessionCookie() cookie.setMaxAge(-1); if (context.getSessionCookie().getPath() != null) { cookie.setPath(context.getSessionCookie().getPath()); } else { String contextPath = context.getEncodedPath(); if ("".equals(contextPath)) { contextPath = "/"; } cookie.setPath(contextPath); } if (context.getSessionCookie().getComment() != null) { cookie.setComment(context.getSessionCookie().getComment()); } if (context.getSessionCookie().getDomain() != null) { cookie.setDomain(context.getSessionCookie().getDomain()); } if (context.getSessionCookie().isHttpOnly()) { cookie.setHttpOnly(true); } if (context.getSessionCookie().isSecure()) { cookie.setSecure(true); } if (connector.getSecure()) { cookie.setSecure(true); } if (trace_) { log_.trace( "Setting cookie with session id:" + sessionId + " & name:" + Globals.SESSION_COOKIE_NAME); } response.addCookie(cookie); } } } // ----------------------------------------------------------------- Manager /** {@inheritDoc} */ public void addPropertyChangeListener(PropertyChangeListener listener) { support_.addPropertyChangeListener(listener); } /** {@inheritDoc} */ public void removePropertyChangeListener(PropertyChangeListener listener) { support_.removePropertyChangeListener(listener); } /** {@inheritDoc} */ public void propertyChange(PropertyChangeEvent evt) { support_.firePropertyChange(evt); } /** {@inheritDoc} */ public void backgroundProcess() { // Always reset trace_ trace_ = log_.isTraceEnabled(); // For other work, only execute every processExpiresFrequency backgroundProcessCount = (backgroundProcessCount + 1) % processExpiresFrequency; if (backgroundProcessCount != 0) return; synchronized (backgroundProcessAllowed) { if (backgroundProcessAllowed.get()) { long start = System.currentTimeMillis(); processExpirationPassivation(); long elapsed = System.currentTimeMillis() - start; processingTime_.addAndGet(elapsed); } } } /** {@inheritDoc} */ public int getActiveSessions() { return (int) getLocalActiveSessionCount(); } /** {@inheritDoc} */ public Container getContainer() { return container_; } /** {@inheritDoc} */ public void setContainer(Container container) { // De-register from the old Container (if any) if ((this.container_ != null) && (this.container_ instanceof Context)) this.container_.removePropertyChangeListener(this); // Default processing provided by our superclass this.container_ = container; // Register with the new Container (if any) if ((this.container_ != null) && (this.container_ instanceof Context)) { setMaxInactiveInterval(((Context) this.container_).getSessionTimeout() * 60); this.container_.addPropertyChangeListener(this); } } /** {@inheritDoc} */ public boolean getDistributable() { return distributable_; } /** {@inheritDoc} */ public void setDistributable(boolean distributable) { this.distributable_ = distributable; } /** {@inheritDoc} */ public int getExpiredSessions() { return expiredCounter_.get(); } /** No-op */ public void setExpiredSessions(int expiredSessions) { // ignored } /** {@inheritDoc} */ public int getMaxActive() { return maxActiveAllowed_; } /** {@inheritDoc} */ public void setMaxActive(int maxActive) { this.maxActiveAllowed_ = maxActive; } /** {@inheritDoc} */ public int getMaxInactiveInterval() { return maxInactiveInterval_; } /** {@inheritDoc} */ public void setMaxInactiveInterval(int interval) { this.maxInactiveInterval_ = interval; } /** {@inheritDoc} */ public long getProcessingTime() { return this.processingTime_.get(); } /** {@inheritDoc} */ public int getRejectedSessions() { return rejectedCounter_.get(); } /** No-op */ public void setRejectedSessions(int rejectedSessions) { // ignored } /** {@inheritDoc} */ public int getSessionAverageAliveTime() { return averageAliveTime.get(); } /** No-op */ public void setSessionAverageAliveTime(int sessionAverageAliveTime) { // ignored } /** {@inheritDoc} */ public int getSessionCounter() { return createdCounter_.get(); } /** No-op */ public void setSessionCounter(int sessionCounter) { // ignored } /** {@inheritDoc} */ public int getSessionIdLength() { return SessionIDGenerator.SESSION_ID_BYTES; } /** No-op */ public void setSessionIdLength(int idLength) { // ignored } /** {@inheritDoc} */ public int getSessionMaxAliveTime() { return maxAliveTime.get(); } /** No-op */ public void setSessionMaxAliveTime(int sessionAliveTime) { // ignored } /** Throws UnsupportedOperationException */ public void load() throws ClassNotFoundException, IOException { throw new UnsupportedOperationException("load() not supported"); } /** Throws UnsupportedOperationException */ public void unload() throws IOException { throw new UnsupportedOperationException("unload() not supported"); } // -------------------------------------------------------------- Lifecycle public void addLifecycleListener(LifecycleListener listener) { lifecycle_.addLifecycleListener(listener); } public LifecycleListener[] findLifecycleListeners() { return lifecycle_.findLifecycleListeners(); } public void removeLifecycleListener(LifecycleListener listener) { lifecycle_.removeLifecycleListener(listener); } /** * Start this Manager * * @throws org.apache.catalina.LifecycleException */ public void start() throws LifecycleException { startManager(); } /** * Stop this Manager * * @throws org.apache.catalina.LifecycleException */ public void stop() throws LifecycleException { // Block for any ongoing backgroundProcess, then disable synchronized (backgroundProcessAllowed) { backgroundProcessAllowed.set(false); } resetStats(); stopManager(); } // ------------------------------------------------------- JBossManagerMBean /** {@inheritDoc} */ public long getActiveSessionCount() { return calcActiveSessions(); } /** {@inheritDoc} */ public String getAlgorithm() { return SessionIDGenerator.SESSION_ID_HASH_ALGORITHM; } /** {@inheritDoc} */ public String getClassName() { return getClass().getName(); } /** {@inheritDoc} */ public long getCreatedSessionCount() { return createdCounter_.get(); } /** {@inheritDoc} */ public long getExpiredSessionCount() { return expiredCounter_.get(); } /** {@inheritDoc} */ public long getLocalActiveSessionCount() { return localActiveCounter_.get(); } /** {@inheritDoc} */ public int getMaxActiveAllowed() { return getMaxActive(); } /** {@inheritDoc} */ public void setMaxActiveAllowed(int maxActive) { setMaxActive(maxActive); } /** {@inheritDoc} */ public int getMaxActiveSessions() { return getMaxActiveAllowed(); } /** {@inheritDoc} */ public long getMaxActiveSessionCount() { return this.maxActiveCounter_.get(); } /** {@inheritDoc} */ public long getMaxLocalActiveSessionCount() { return maxLocalActiveCounter_.get(); } /** {@inheritDoc} */ public String getName() { return getClass().getSimpleName(); } /** {@inheritDoc} */ public int getProcessExpiresFrequency() { return this.processExpiresFrequency; } /** {@inheritDoc} */ public void setProcessExpiresFrequency(int frequency) { this.processExpiresFrequency = frequency; } /** {@inheritDoc} */ public long getRejectedSessionCount() { return rejectedCounter_.get(); } /** {@inheritDoc} */ public ReplicationStatistics getReplicationStatistics() { return stats_; } /** {@inheritDoc} */ public long getTimeSinceLastReset() { return (System.currentTimeMillis() - timeSinceLastReset_) / (1000L); } /** {@inheritDoc} */ public String reportReplicationStatistics() { StringBuffer tmp = new StringBuffer(); tmp.append("<table><tr>"); tmp.append("<th>sessionID</th>"); tmp.append("<th>replicationCount</th>"); tmp.append("<th>minPassivationTime</th>"); tmp.append("<th>maxPassivationTime</th>"); tmp.append("<th>totalPassivationTime</th>"); tmp.append("<th>minReplicationTime</th>"); tmp.append("<th>maxReplicationTime</th>"); tmp.append("<th>totalReplicationlTime</th>"); tmp.append("<th>loadCount</th>"); tmp.append("<th>minLoadTime</th>"); tmp.append("<th>maxLoadTime</th>"); tmp.append("<th>totalLoadTime</th>"); Map<String, ReplicationStatistics.TimeStatistic> copy = new HashMap<String, ReplicationStatistics.TimeStatistic>(stats_.getStats()); for (Map.Entry<String, ReplicationStatistics.TimeStatistic> entry : copy.entrySet()) { ReplicationStatistics.TimeStatistic stat = (ReplicationStatistics.TimeStatistic) entry.getValue(); if (stat != null) { tmp.append("<tr><td>"); tmp.append(entry.getKey()); tmp.append("</td><td>"); tmp.append(stat.replicationCount); tmp.append("</td><td>"); tmp.append(stat.minPassivationTime); tmp.append("</td><td>"); tmp.append(stat.maxPassivationTime); tmp.append("</td><td>"); tmp.append(stat.totalPassivationTime); tmp.append("</td><td>"); tmp.append(stat.minReplicationTime); tmp.append("</td><td>"); tmp.append(stat.maxReplicationTime); tmp.append("</td><td>"); tmp.append(stat.totalReplicationlTime); tmp.append("</td><td>"); tmp.append(stat.loadCount); tmp.append("</td><td>"); tmp.append(stat.minLoadTime); tmp.append("</td><td>"); tmp.append(stat.maxLoadTime); tmp.append("</td><td>"); tmp.append(stat.totalLoadlTime); tmp.append("</td></tr>"); } } tmp.append("</table>"); copy.clear(); return tmp.toString(); } /** {@inheritDoc} */ public String reportReplicationStatisticsCSV() { StringBuffer tmp = createCSVHeader(); Map<String, ReplicationStatistics.TimeStatistic> copy = new HashMap<String, ReplicationStatistics.TimeStatistic>(stats_.getStats()); for (Map.Entry<String, ReplicationStatistics.TimeStatistic> entry : copy.entrySet()) { ReplicationStatistics.TimeStatistic stat = (ReplicationStatistics.TimeStatistic) entry.getValue(); if (stat != null) { tmp.append("\n"); tmp.append(entry.getKey()); tmp.append(","); tmp.append(stat.replicationCount); tmp.append(","); tmp.append(stat.minPassivationTime); tmp.append(","); tmp.append(stat.maxPassivationTime); tmp.append(","); tmp.append(stat.totalPassivationTime); tmp.append(","); tmp.append(stat.minReplicationTime); tmp.append(","); tmp.append(stat.maxReplicationTime); tmp.append(","); tmp.append(stat.totalReplicationlTime); tmp.append(","); tmp.append(stat.loadCount); tmp.append(","); tmp.append(stat.minLoadTime); tmp.append(","); tmp.append(stat.maxLoadTime); tmp.append(","); tmp.append(stat.totalLoadlTime); } } copy.clear(); return tmp.toString(); } /** {@inheritDoc} */ public String reportReplicationStatisticsCSV(String sessionId) { StringBuffer tmp = createCSVHeader(); Map<String, ReplicationStatistics.TimeStatistic> stats = stats_.getStats(); ReplicationStatistics.TimeStatistic stat = (ReplicationStatistics.TimeStatistic) stats.get(sessionId); if (stat != null) { tmp.append("\n"); tmp.append(sessionId); tmp.append(","); tmp.append(stat.replicationCount); tmp.append(","); tmp.append(stat.minPassivationTime); tmp.append(","); tmp.append(stat.maxPassivationTime); tmp.append(","); tmp.append(stat.totalPassivationTime); tmp.append(","); tmp.append(stat.minReplicationTime); tmp.append(","); tmp.append(stat.maxReplicationTime); tmp.append(","); tmp.append(stat.totalReplicationlTime); tmp.append(","); tmp.append(stat.loadCount); tmp.append(","); tmp.append(stat.minLoadTime); tmp.append(","); tmp.append(stat.maxLoadTime); tmp.append(","); tmp.append(stat.totalLoadlTime); } return tmp.toString(); } /** {@inheritDoc} */ public void resetStats() { stats_.resetStats(); maxActiveCounter_.set(localActiveCounter_.get()); rejectedCounter_.set(0); createdCounter_.set(0); expiredCounter_.set(0); processingTime_.set(0); maxAliveTime.set(0); averageAliveTime.set(0); duplicates_.set(0); timeSinceLastReset_ = System.currentTimeMillis(); } // ------------------------------------------------------------------ Public /** Gets the JMX <code>ObjectName</code> under which our <code>TreeCache</code> is registered. */ public ObjectName getObjectName() { return objectName_; } // ------------------------------------------------------------------ Protected /** Go through all sessions and look if they have expired or need to be passivated. */ protected abstract void processExpirationPassivation(); /** Get the total number of active sessions */ protected abstract int getTotalActiveSessions(); /** * Calculates the number of active sessions, and updates the max # of local active sessions and * max # of sessions. * * <p>Call this method when a new session is added or when an accurate count of active sessions is * needed. * * @return the size of the sessions map + the size of the unloaded sessions map - the count of * passivated sessions */ protected int calcActiveSessions() { localActiveCounter_.set(sessions_.size()); int active = localActiveCounter_.get(); int maxLocal = maxLocalActiveCounter_.get(); while (active > maxLocal) { if (!maxLocalActiveCounter_.compareAndSet(maxLocal, active)) { maxLocal = maxLocalActiveCounter_.get(); } } int count = getTotalActiveSessions(); int max = maxActiveCounter_.get(); while (count > max) { if (!maxActiveCounter_.compareAndSet(max, count)) { max = maxActiveCounter_.get(); // Something changed, so reset our count count = getTotalActiveSessions(); } } return count; } /** * Returns the given session if it is being actively managed by this manager. An actively managed * session is on that was either created on this server, brought into local management by a call * to {@link #findLocalSession(String)} or brought into local management by a call to {@link * #findSessions()}. * * @param realId the session id, with any trailing jvmRoute removed. * @see #getRealId(String) */ protected ClusteredSession<? extends OutgoingDistributableSessionData> findLocalSession( String realId) { return sessions_.get(realId); } /** * Returns all the sessions that are being actively managed by this manager. This includes those * that were created on this server, those that were brought into local management by a call to * {@link #findLocalSession(String)} as well as all sessions brought into local management by a * call to {@link #findSessions()}. */ protected ClusteredSession<? extends OutgoingDistributableSessionData>[] findLocalSessions() { Collection<ClusteredSession<? extends OutgoingDistributableSessionData>> coll = sessions_.values(); @SuppressWarnings("unchecked") ClusteredSession<? extends OutgoingDistributableSessionData>[] sess = new ClusteredSession[coll.size()]; return coll.toArray(sess); } /** * Get a new session-id from the distributed store * * @return new session-id */ protected String getNextId() { return sessionIDGenerator_.getSessionId(); } /** * Updates statistics to reflect that a session with a given "alive time" has been expired. * * @param sessionAliveTime number of ms from when the session was created to when it was expired. */ protected void sessionExpired(int sessionAliveTime) { int current = maxAliveTime.get(); while (sessionAliveTime > current) { if (maxAliveTime.compareAndSet(current, sessionAliveTime)) break; else current = maxAliveTime.get(); } expiredCounter_.incrementAndGet(); int newAverage; do { int expCount = expiredCounter_.get(); current = averageAliveTime.get(); newAverage = ((current * (expCount - 1)) + sessionAliveTime) / expCount; } while (averageAliveTime.compareAndSet(current, newAverage) == false); } /** Register this Manager with JMX. */ protected void registerManagerMBean() { try { MBeanServer server = getMBeanServer(); String domain; if (container_ instanceof ContainerBase) { domain = ((ContainerBase) container_).getDomain(); } else { domain = server.getDefaultDomain(); } String hostName = ((Host) container_.getParent()).getName(); hostName = (hostName == null) ? "localhost" : hostName; ObjectName clusterName = new ObjectName( domain + ":type=Manager,host=" + hostName + ",path=" + ((Context) container_).getPath()); if (server.isRegistered(clusterName)) { log_.warn("MBean " + clusterName + " already registered"); return; } objectName_ = clusterName; server.registerMBean(this, clusterName); } catch (Exception ex) { log_.error("Could not register " + getClass().getSimpleName() + " to MBeanServer", ex); } } /** Unregister this Manager from the JMX server. */ protected void unregisterManagerMBean() { if (mserver_ != null && objectName_ != null) { try { mserver_.unregisterMBean(objectName_); } catch (Exception e) { log_.error("Could not unregister " + getClass().getSimpleName() + " from MBeanServer", e); } } } /** * Get the current MBean Server. * * @return * @throws Exception */ protected MBeanServer getMBeanServer() throws Exception { if (mserver_ == null) { mserver_ = Registry.getRegistry(null, null).getMBeanServer(); } return mserver_; } /** * Prepare for the beginning of active use of the public methods of this component. This method * should be called after <code>configure()</code>, and before any of the public methods of the * component are utilized. * * @throws IllegalStateException if this component has already been started_ * @throws org.apache.catalina.LifecycleException if this component detects a fatal error that * prevents this component from being used */ protected void startManager() throws LifecycleException { log_.debug("Starting JBossManager"); // Validate and update our current component state if (started_) throw new LifecycleException("JBossManager alreadyStarted"); backgroundProcessAllowed.set(true); lifecycle_.fireLifecycleEvent(START_EVENT, null); started_ = true; // register ClusterManagerMBean to the MBeanServer registerManagerMBean(); } /** * Gracefully terminate the active use of the public methods of this component. This method should * be the last one called on a given instance of this component. * * @throws IllegalStateException if this component has not been started_ * @throws org.apache.catalina.LifecycleException if this component detects a fatal error that * needs to be reported */ protected void stopManager() throws LifecycleException { log_.debug("Stopping JBossManager"); // Validate and update our current component state if (!started_) throw new LifecycleException("JBossManager notStarted"); lifecycle_.fireLifecycleEvent(STOP_EVENT, null); started_ = false; // unregister from the MBeanServer unregisterManagerMBean(); } // ----------------------------------------------------------------- Private private StringBuffer createCSVHeader() { StringBuffer tmp = new StringBuffer(); tmp.append("sessionID,"); tmp.append("replicationCount,"); tmp.append("minPassivationTime,"); tmp.append("maxPassivationTime,"); tmp.append("totalPassivationTime,"); tmp.append("minReplicationTime,"); tmp.append("maxReplicationTime,"); tmp.append("totalReplicationlTime,"); tmp.append("loadCount,"); tmp.append("minLoadTime,"); tmp.append("maxLoadTime,"); tmp.append("totalLoadTime"); return tmp; } /** * Retrieve the enclosing Engine for this Manager. * * @return an Engine object (or null). */ private Engine getEngine() { Engine e = null; for (Container c = getContainer(); e == null && c != null; c = c.getParent()) { if (c != null && c instanceof Engine) { e = (Engine) c; } } return e; } }