/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.env.deploy; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.config.ConfigException; import com.caucho.config.types.Period; import com.caucho.lifecycle.Lifecycle; import com.caucho.lifecycle.LifecycleListener; import com.caucho.lifecycle.LifecycleState; import com.caucho.loader.DynamicClassLoader; import com.caucho.util.Alarm; import com.caucho.util.L10N; import com.caucho.vfs.Dependency; /** * DeployController controls the lifecycle of the DeployInstance. */ abstract public class DeployController<I extends DeployInstance> implements DeployControllerApi<I>, Dependency, DeployActionHandler { private static final Logger log = Logger.getLogger(DeployController.class.getName()); private static final L10N L = new L10N(DeployController.class); public static final long REDEPLOY_CHECK_INTERVAL = 60000; private ClassLoader _parentLoader; private final String _id; private final String _idStage; private final String _idType; private final String _idKey; private DeployMode _startupMode = DeployMode.DEFAULT; private DeployMode _redeployMode = DeployMode.DEFAULT; private int _startupPriority = Integer.MAX_VALUE; private DeployControllerType _controllerType = DeployControllerType.STATIC; private DeployControllerStrategy _strategy = StartManualRedeployManualStrategy.STRATEGY; protected final Lifecycle _lifecycle; private DeployControllerAlarm<DeployController<I>> _alarm; private DeployTagItem _deployTagItem; private long _waitForActiveTimeout = 10000L; private long _redeployCheckInterval = REDEPLOY_CHECK_INTERVAL; private long _startTime; private final AtomicReference<I> _deployInstanceRef = new AtomicReference<I>(); protected DeployController(String id) { this(id, null); } protected DeployController(String id, ClassLoader parentLoader) { _id = id; if (parentLoader == null) parentLoader = Thread.currentThread().getContextClassLoader(); _parentLoader = parentLoader; _lifecycle = new Lifecycle(getLog(), toString(), Level.FINEST); int p1 = id.indexOf('/'); _idStage = id.substring(0, p1); int p2 = id.indexOf('/', p1 + 1); _idType = id.substring(p1 + 1, p2); _idKey = id.substring(p2 + 1); } /** * Creates an instance. */ abstract protected I instantiateDeployInstance(); /** * Returns the controller's id. */ @Override public final String getId() { return _id; } public final String getIdStage() { return _idStage; } public final String getIdType() { return _idType; } public final String getIdKey() { return _idKey; } /** * Returns the parent class loader. */ public ClassLoader getParentClassLoader() { return _parentLoader; } @Override public DeployControllerType getControllerType() { return _controllerType; } public void setControllerType(DeployControllerType type) { _controllerType = type; } /** * Sets the startup mode. */ public void setStartupMode(DeployMode mode) { _startupMode = mode; } /** * Sets the startup priority. */ public void setStartupPriority(int priority) { _startupPriority = priority; } /** * Gets the startup priority. */ @Override public int getStartupPriority() { return _startupPriority; } /** * Merges with the new controller information */ @Override public void merge(DeployControllerApi<I> newController) { } /** * Returns the startup mode. */ public DeployMode getStartupMode() { return _startupMode; } /** * Sets the redeploy mode. */ public void setRedeployMode(DeployMode mode) { _redeployMode = mode; } /** * Merge the redeploy mode. */ public void mergeRedeployMode(DeployMode mode) { if (mode == null || DeployMode.DEFAULT.equals(mode)) return; _redeployMode = mode; } /** * Returns the redeploy mode. */ public DeployMode getRedeployMode() { return _redeployMode; } /** * Sets the redeploy-check-interval */ public void mergeRedeployCheckInterval(long interval) { if (interval != REDEPLOY_CHECK_INTERVAL) _redeployCheckInterval = interval; } /** * Sets the redeploy-check-interval */ public void setRedeployCheckInterval(Period period) { _redeployCheckInterval = period.getPeriod(); if (_redeployCheckInterval < 0) _redeployCheckInterval = Period.INFINITE; if (_redeployCheckInterval < 5000) _redeployCheckInterval = 5000; } /** * Gets the redeploy-check-interval */ public long getRedeployCheckInterval() { return _redeployCheckInterval; } /** * Sets the delay time waiting for a restart */ public void setActiveWaitTimeMillis(long wait) { _waitForActiveTimeout = wait; } public long getActiveWaitTime() { return _waitForActiveTimeout; } /** * Returns true if */ @Override public boolean isNameMatch(String name) { return getId().equals(name); } /** * Returns the start time of the entry. */ final public long getStartTime() { return _startTime; } /** * Initialize the entry. */ @Override public final boolean init() { if (! _lifecycle.toInitializing()) return false; Thread thread = Thread.currentThread(); ClassLoader oldLoader = thread.getContextClassLoader(); try { thread.setContextClassLoader(getParentClassLoader()); initBegin(); switch (_startupMode) { case MANUAL: { if (_redeployMode == DeployMode.AUTOMATIC) { throw new IllegalStateException(L.l("startup='manual' and redeploy='automatic' is an unsupported combination.")); } else _strategy = StartManualRedeployManualStrategy.create(); break; } case LAZY: { if (_redeployMode == DeployMode.MANUAL) _strategy = StartLazyRedeployManualStrategy.create(); else _strategy = StartLazyRedeployAutomaticStrategy.create(); break; } default: { if (_redeployMode == DeployMode.MANUAL) _strategy = StartAutoRedeployManualStrategy.create(); else _strategy = StartAutoRedeployAutoStrategy.create(); } } DeployControllerService deployService = DeployControllerService.getCurrent(); _deployTagItem = deployService.addTag(getId()); _deployTagItem.addActionHandler(this); initEnd(); return _lifecycle.toInit(); } finally { thread.setContextClassLoader(oldLoader); } } /** * Initial calls for init. */ protected void initBegin() { } /** * Final calls for init. */ protected void initEnd() { } /** * Returns the state name. */ @Override public final LifecycleState getState() { return _lifecycle.getState(); } /** * Returns true if the instance has been idle for longer than its timeout. * * @return true if idle */ public final boolean isIdleTimeout() { DeployInstance instance = getDeployInstanceImpl(); if (instance != null) return instance.isDeployIdle(); else return false; } // // dependency/modified // /** * Returns true if the entry is modified. */ @Override public boolean isModified() { if (isControllerModified()) { return true; } DeployInstance instance = getDeployInstanceImpl(); return instance == null || instance.isModified(); } /** * Returns true if the entry is modified. */ public boolean isModifiedNow() { if (isControllerModifiedNow()) return true; DeployInstance instance = getDeployInstanceImpl(); return instance == null || instance.isModifiedNow(); } /** * Log the reason for modification */ @Override final public boolean logModified(Logger log) { if (controllerLogModified(log)) { return true; } DeployInstance instance = getDeployInstanceImpl(); if (instance != null) { Thread thread = Thread.currentThread(); ClassLoader loader = thread.getContextClassLoader(); try { thread.setContextClassLoader(instance.getClassLoader()); return instance.logModified(log); } finally { thread.setContextClassLoader(loader); } } else return false; } protected boolean isControllerModified() { return false; } protected boolean isControllerModifiedNow() { return false; } protected boolean controllerLogModified(Logger log) { return false; } /** * Returns the current instance. */ @Override public I getDeployInstance() { if (_lifecycle.isActive() || _lifecycle.isError()) return getDeployInstanceImpl(); else return null; } @Override public I getActiveDeployInstance() { _lifecycle.waitForActive(getActiveWaitTime()); return getDeployInstanceImpl(); } /** * Returns the current instance. */ public I getDeployInstanceImpl() { return _deployInstanceRef.get(); } /** * Returns the current instance. */ protected final I createDeployInstance() { Thread thread = Thread.currentThread(); ClassLoader oldLoader = thread.getContextClassLoader(); try { thread.setContextClassLoader(getParentClassLoader()); return instantiateDeployInstance(); } finally { thread.setContextClassLoader(oldLoader); } } @Override public void addLifecycleListener(LifecycleListener listener) { _lifecycle.addListener(listener); } /** * Starts the entry on initialization */ @Override public void startOnInit() { if (! _lifecycle.isAfterInit()) throw new IllegalStateException(L.l("startOnInit must be called after init (in '{0}')", _lifecycle.getStateName())); Thread thread = Thread.currentThread(); ClassLoader loader = thread.getContextClassLoader(); try { thread.setContextClassLoader(getParentClassLoader()); _strategy.startOnInit(this); _alarm = new DeployControllerAlarm<DeployController<I>>(this, _redeployCheckInterval); } finally { thread.setContextClassLoader(loader); } } /** * Force an instance start from an admin command. */ @Override public final void start() { _strategy.start(this); } /** * Stops the controller from an admin command. */ @Override public final void stop() { _strategy.stop(this); } /** * Force an instance restart from an admin command. */ @Override public final void restart() { _strategy.stop(this); _strategy.start(this); } /** * Update the controller from an admin command. */ @Override public final void update() { _strategy.update(this); } /** * Returns the instance for a top-level request * @return the request object or null for none. */ @Override public final I request() { if (_lifecycle.isDestroyed()) return null; else if (_strategy != null) { I instance = _strategy.request(this); return instance; } else return null; } /** * Returns the instance for a subrequest. * * @return the request object or null for none. */ @Override public final I subrequest() { if (_lifecycle.isDestroyed()) return null; else if (_strategy != null) { I instance = _strategy.subrequest(this); return instance; } else return null; } /** * Restarts the instance * * @return the new instance */ final I restartImpl() { if (_lifecycle.isAllowStopFromRestart()) stopImpl(); return startImpl(); } /** * Starts the entry. */ protected I startImpl() { assert(_lifecycle.isAfterInit()); if (DynamicClassLoader.isModified(_parentLoader)) { I instance = _deployInstanceRef.getAndSet(null); if (instance != null) instance.destroy(); return null; } I deployInstance = null; Thread thread = Thread.currentThread(); ClassLoader oldLoader = thread.getContextClassLoader(); ClassLoader loader = null; boolean isStarting = false; boolean isActive = false; try { thread.setContextClassLoader(_parentLoader); deployInstance = createDeployInstance(); if (deployInstance == null) throw new NullPointerException(getClass().getName()); loader = deployInstance.getClassLoader(); thread.setContextClassLoader(loader); isStarting = _lifecycle.toStarting(); if (! isStarting || ! _deployInstanceRef.compareAndSet(null, deployInstance)) { try { deployInstance.destroy(); } catch (Throwable e) { log.log(Level.FINEST, e.toString(), e); } return getDeployInstance(); } preConfigureInstance(deployInstance); configureInstance(deployInstance); postConfigureInstance(deployInstance); deployInstance.start(); _deployTagItem.onStart(); isActive = true; _startTime = Alarm.getCurrentTime(); } catch (ConfigException e) { log.log(Level.FINEST, e.toString(), e); onError(e); if (deployInstance != null) { log.finer(e.toString()); deployInstance.setConfigException(e); } else { log.severe(e.toString()); } } catch (Throwable e) { log.log(Level.FINEST, e.toString(), e); onError(e); if (deployInstance != null) { log.finer(e.toString()); deployInstance.setConfigException(e); } else { log.log(Level.SEVERE, e.toString(), e); } } finally { if (isStarting) { if (isActive) { _lifecycle.toActive(); onActive(); } else _lifecycle.toError(); onStartComplete(); // server/ if (loader instanceof DynamicClassLoader) ((DynamicClassLoader) loader).clearModified(); } thread.setContextClassLoader(oldLoader); } return deployInstance; } /** * Stops the current instance, putting it in the lazy state. */ protected void stopLazyImpl() { if (! _lifecycle.isIdle()) { stopImpl(); } _lifecycle.toIdle(); } /** * Stops the current instance. */ protected void stopImpl() { Thread thread = Thread.currentThread(); ClassLoader oldLoader = thread.getContextClassLoader(); I oldInstance = _deployInstanceRef.get(); boolean isStopping = false; try { if (oldInstance != null) thread.setContextClassLoader(oldInstance.getClassLoader()); isStopping = _lifecycle.toStopping(); _lifecycle.toStop(); if (! isStopping) return; if (oldInstance != null && _deployInstanceRef.compareAndSet(oldInstance, null)) { destroyInstance(oldInstance); } } finally { if (isStopping) { onStop(); } thread.setContextClassLoader(oldLoader); } return; } protected void destroyInstance(I instance) { if (instance != null) instance.destroy(); } // // state callbacks // protected void onActive() { } protected void onError(Throwable e) { } protected void onStartComplete() { } protected void onStop() { } /** * Before instance configuration */ protected void preConfigureInstance(I deployInstance) throws Exception { } /** * Configuration of the instance */ protected void configureInstance(I deployInstance) throws Exception { } /** * After instance configuration */ protected void postConfigureInstance(I deployInstance) throws Exception { } // // DeployActionHandler // @Override public void toStart() { start(); } @Override public void toStop() { stop(); } @Override public void toRestart() { restart(); } @Override public final void alarm() { _strategy.alarm(this); } @Override public final void close() { destroy(); } /** * Destroys the entry. */ protected boolean destroy() { if (_lifecycle.isAfterInit()) stop(); if (! _lifecycle.toDestroy()) return false; DeployControllerAlarm<DeployController<I>> alarm = _alarm; _alarm = null; if (alarm != null) { alarm.close(); } _deployTagItem.removeActionHandler(this); onDestroy(); return true; } protected void onDestroy() { } /** * Returns the appropriate log for debugging. */ protected Logger getLog() { return log; } /** * Returns the entry's debug name. */ @Override public String toString() { String className = getClass().getName(); int p = className.lastIndexOf('.'); return className.substring(p + 1) + "[" + getId() + "]"; } }