/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.system.controller.legacy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import javax.management.JMException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanRegistration; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.ObjectName; import org.jboss.deployment.DeploymentException; import org.jboss.deployment.DeploymentInfo; import org.jboss.deployment.DeploymentState; import org.jboss.logging.Logger; import org.jboss.mx.server.ServerConstants; import org.jboss.mx.util.JBossNotificationBroadcasterSupport; import org.jboss.mx.util.JMXExceptionDecoder; import org.jboss.mx.util.ObjectNameFactory; import org.jboss.system.Service; import org.jboss.system.ServiceContext; import org.jboss.system.ServiceFactory; import org.jboss.system.ServiceMBean; import org.w3c.dom.Element; /** * This is the main Service Controller. A controller can deploy a service to a * jboss.system It installs by delegating, it configures by delegating * * @see org.jboss.system.Service * * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a> * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a> * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a> * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a> * @version $Revision: 85945 $ */ public class OldServiceController extends JBossNotificationBroadcasterSupport implements OldServiceControllerMBean, MBeanRegistration { /** The ObjectName of the default loader repository */ public static final ObjectName DEFAULT_LOADER_REPOSITORY = ObjectNameFactory.create(ServerConstants.DEFAULT_LOADER_NAME); /** The operation name for lifecycle */ public static final String JBOSS_INTERNAL_LIFECYCLE = "jbossInternalLifecycle"; /** The signature for lifecycle operations */ public static final String[] JBOSS_INTERNAL_LIFECYCLE_SIG = new String[] { String.class.getName() }; /** Class logger. */ private static final Logger log = Logger.getLogger(OldServiceController.class); /** A callback to the JMX MBeanServer */ protected MBeanServer server; /** Creator, helper class to instantiate MBeans **/ protected OldServiceCreator creator; /** Configurator, helper class to configure MBeans **/ protected OldServiceConfigurator configurator; /** ObjectName to ServiceContext map **/ protected Map<ObjectName, ServiceContext> nameToServiceMap = Collections.synchronizedMap(new HashMap<ObjectName, ServiceContext>()); /** A linked list of services in the order they were created **/ protected List<ServiceContext> installedServices = new LinkedList<ServiceContext>(); public List<ServiceContext> listDeployed() { return new ArrayList<ServiceContext>(installedServices); } public List<ServiceContext> listIncompletelyDeployed() { List<ServiceContext> id = new ArrayList<ServiceContext>(); for (Iterator<ServiceContext> i = installedServices.iterator(); i.hasNext();) { ServiceContext sc = i.next(); if ( sc.state != ServiceContext.CREATED && sc.state != ServiceContext.RUNNING && sc.state != ServiceContext.STOPPED && sc.state != ServiceContext.DESTROYED ) { id.add(sc); } } return id; } public List<ObjectName> listDeployedNames() { List<ObjectName> names = new ArrayList<ObjectName>(installedServices.size()); for (Iterator<ServiceContext> i = installedServices.iterator(); i.hasNext();) { ServiceContext ctx = i.next(); names.add(ctx.objectName); } return names; } public String listConfiguration(ObjectName[] objectNames) throws Exception { return configurator.getConfiguration(objectNames); } public void validateDeploymentState(DeploymentInfo di, DeploymentState state) { ArrayList<ObjectName> mbeans = new ArrayList<ObjectName>(di.mbeans); if (di.deployedObject != null) mbeans.add(di.deployedObject); boolean mbeansStateIsValid = true; for (int m = 0; m < mbeans.size(); m++) { ObjectName serviceName = mbeans.get(m); ServiceContext ctx = this.getServiceContext(serviceName); if (ctx != null && state == DeploymentState.STARTED) mbeansStateIsValid &= ctx.state == ServiceContext.RUNNING; } if (mbeansStateIsValid == true) di.state = state; } public synchronized List<ObjectName> install(Element config, ObjectName loaderName) throws DeploymentException { List<ObjectName> mbeans = configurator.install(config, loaderName); for (Iterator<ObjectName> i = mbeans.iterator(); i.hasNext();) { ObjectName mbean = i.next(); installedServices.add(createServiceContext(mbean)); } return mbeans; } public synchronized void register(ObjectName serviceName) throws Exception { register(serviceName, null); } public synchronized void register(ObjectName serviceName, Collection<ObjectName> depends) throws Exception { if (serviceName == null) { log.warn("Ignoring request to register null service: ", new Exception("STACKTRACE")); return; } log.debug("Registering service " + serviceName); ServiceContext ctx = createServiceContext(serviceName); register(ctx, depends); } public synchronized void create(ObjectName serviceName) throws Exception { create(serviceName, null); } public synchronized void create(ObjectName serviceName, Collection<ObjectName> depends) throws Exception { if (serviceName == null) { log.warn("Ignoring request to create null service: ", new Exception("STACKTRACE")); return; } log.debug("Creating service " + serviceName); ServiceContext ctx = createServiceContext(serviceName); // Register the context and its dependencies if necessary register(ctx, depends); // If we are already created (can happen in dependencies) or failed just return if (ctx.state == ServiceContext.CREATED || ctx.state == ServiceContext.RUNNING || ctx.state == ServiceContext.FAILED) { log.debug("Ignoring create request for service: " + ctx.objectName); return; } // JSR 77, and to avoid circular dependencies int oldState = ctx.state; ctx.state = ServiceContext.CREATED; // Are all the mbeans I depend on created? if not just return for (Iterator iterator = ctx.iDependOn.iterator(); iterator.hasNext();) { ServiceContext sc = (ServiceContext) iterator.next(); int state = sc.state; // A dependent is not created or running if (!(state == ServiceContext.CREATED || state == ServiceContext.RUNNING)) { log.debug("waiting in create of " + serviceName + " waiting on " + sc.objectName); ctx.state = oldState; return; } } // Call create on the service Proxy try { ctx.proxy.create(); sendControllerNotification(ServiceMBean.CREATE_EVENT, serviceName); } catch (Throwable e) { ctx.state = ServiceContext.FAILED; ctx.problem = e; log.warn("Problem creating service " + serviceName, e); return; } // Those that depend on me are waiting for my creation, recursively create them log.debug("Creating dependent components for: " + serviceName + " dependents are: " + ctx.dependsOnMe); ArrayList<ServiceContext> tmp = new ArrayList<ServiceContext>(ctx.dependsOnMe); for (int n = 0; n < tmp.size(); n++) { // marcf fixme circular dependencies? ServiceContext ctx2 = tmp.get(n); create(ctx2.objectName); } tmp.clear(); } public synchronized void start(ObjectName serviceName) throws Exception { if (serviceName == null) { log.warn("Ignoring request to start null service: ", new Exception("STACKTRACE")); return; } log.debug("starting service " + serviceName); ServiceContext ctx = createServiceContext(serviceName); if (!installedServices.contains(ctx)) installedServices.add(ctx); // If we are already started (can happen in dependencies) just return if (ctx.state == ServiceContext.RUNNING || ctx.state == ServiceContext.FAILED) { log.debug("Ignoring start request for service: " + ctx.objectName); return; } // Start() is called before create(), so call create() to compensate if (ctx.state != ServiceContext.CREATED && ctx.state != ServiceContext.STOPPED) { log.debug("Start requested before create, calling create now for service: " + serviceName); create(serviceName); } // Get the fancy service proxy (for the lifecycle API) if (ctx.proxy == null) ctx.proxy = getServiceProxy(ctx.objectName, null); // JSR 77, and to avoid circular dependencies int oldState = ctx.state; ctx.state = ServiceContext.RUNNING; // Are all the mbeans I depend on started? if not just return for (Iterator iterator = ctx.iDependOn.iterator(); iterator.hasNext();) { ServiceContext sctx = (ServiceContext) iterator.next(); int state = sctx.state; // A dependent is not running if (!(state == ServiceContext.RUNNING)) { log.debug("waiting in start " + serviceName + " on " + sctx.objectName); ctx.state = oldState; return; } } // Call start on the service Proxy try { ctx.proxy.start(); sendControllerNotification(ServiceMBean.START_EVENT, serviceName); } catch (Throwable e) { ctx.state = ServiceContext.FAILED; ctx.problem = e; log.warn("Problem starting service " + serviceName, e); return; } // Those that depend on me are waiting for my start, recursively start them log.debug("Starting dependent components for: " + serviceName + " dependent components: " + ctx.dependsOnMe); ArrayList<ServiceContext> tmp = new ArrayList<ServiceContext>(ctx.dependsOnMe); for (int n = 0; n < tmp.size(); n++) { // marcf fixme circular dependencies? ServiceContext ctx2 = tmp.get(n); start(ctx2.objectName); } tmp.clear(); } public void restart(ObjectName serviceName) throws Exception { if (serviceName == null) { log.warn("Ignoring request to restart null service: ", new Exception("STACKTRACE")); return; } log.debug("restarting service " + serviceName); stop(serviceName); start(serviceName); } public void stop(ObjectName serviceName) throws Exception { if (serviceName == null) { log.warn("Ignoring request to stop null service: ", new Exception("STACKTRACE")); return; } ServiceContext ctx = nameToServiceMap.get(serviceName); log.debug("stopping service: " + serviceName); if (ctx == null) { log.warn("Ignoring request to stop nonexistent service: " + serviceName); return; } // If we are already stopped (can happen in dependencies) just return if (ctx.state != ServiceContext.RUNNING) return; // JSR 77 and to avoid circular dependencies ctx.state = ServiceContext.STOPPED; log.debug("stopping dependent services for: " + serviceName + " dependent services are: " + ctx.dependsOnMe); ArrayList<ServiceContext> tmp = new ArrayList<ServiceContext>(ctx.dependsOnMe); for (int n = 0; n < tmp.size(); n++) { // stop all the mbeans that depend on me ServiceContext ctx2 = tmp.get(n); ObjectName other = ctx2.objectName; stop(other); } tmp.clear(); // Call stop on the service Proxy if (ctx.proxy != null) { try { ctx.proxy.stop(); sendControllerNotification(ServiceMBean.STOP_EVENT, serviceName); } catch (Throwable e) { ctx.state = ServiceContext.FAILED; ctx.problem = e; log.warn("Problem stopping service " + serviceName, e); } } } public void destroy(ObjectName serviceName) throws Exception { if (serviceName == null) { log.warn("Ignoring request to destroy null service: ", new Exception("STACKTRACE")); return; } ServiceContext ctx = nameToServiceMap.get(serviceName); log.debug("destroying service: " + serviceName); if (ctx == null) { log.warn("Ignoring request to destroy nonexistent service: " + serviceName); return; } // If we are already destroyed (can happen in dependencies) just return if (ctx.state == ServiceContext.DESTROYED || ctx.state == ServiceContext.NOTYETINSTALLED) return; // If we are still running, stop service first if (ctx.state == ServiceContext.RUNNING) { log.debug("Destroy requested before stop, calling stop now for service: " + serviceName); stop(serviceName); } // JSR 77, and to avoid circular dependencies ctx.state = ServiceContext.DESTROYED; log.debug("destroying dependent services for: " + serviceName + " dependent services are: " + ctx.dependsOnMe); ArrayList<ServiceContext> tmp = new ArrayList<ServiceContext>(ctx.dependsOnMe); for (int n = 0; n < tmp.size(); n++) { // destroy all the mbeans that depend on me ServiceContext ctx2 = tmp.get(n); ObjectName other = ctx2.objectName; destroy(other); } tmp.clear(); // Call destroy on the service Proxy if (ctx.proxy != null) { try { ctx.proxy.destroy(); sendControllerNotification(ServiceMBean.DESTROY_EVENT, serviceName); } catch (Throwable e) { ctx.state = ServiceContext.FAILED; ctx.problem = e; log.warn("Problem destroying service " + serviceName, e); } } } public void remove(ObjectName objectName) throws Exception { if (objectName == null) { log.warn("Ignoring request to remove null service: ", new Exception("STACKTRACE")); return; } ServiceContext ctx = nameToServiceMap.get(objectName); if (ctx == null) { log.debug("Ignoring request to remove nonexistent service: " + objectName); return; } log.debug("removing service: " + objectName); // Notify those that think I depend on them Iterator iterator = ctx.iDependOn.iterator(); while (iterator.hasNext()) { ServiceContext iDependOnContext = (ServiceContext) iterator.next(); iDependOnContext.dependsOnMe.remove(ctx); // Remove any context whose only reason for existence is that // we depend on it, i.e. it otherwise unknown to the system if (iDependOnContext.state == ServiceContext.NOTYETINSTALLED && iDependOnContext.dependsOnMe.size() == 0) { nameToServiceMap.remove(iDependOnContext.objectName); log.debug("Removing context for nonexistent service it is " + "no longer recording dependencies: " + iDependOnContext); } } //We remove all traces of our dependency configuration, since we //don't know what will show up the next time we are deployed. ctx.iDependOn.clear(); // Do we have a deployed MBean? if (server.isRegistered(objectName)) { log.debug("removing " + objectName + " from server"); // Remove the context, unless it is still recording dependencies if (ctx.dependsOnMe.size() == 0) nameToServiceMap.remove(objectName); else { log.debug("Context not removed, it is recording " + "dependencies: " + ctx); ctx.proxy = null; } // remove the mbean from the instaled ones installedServices.remove(ctx); creator.remove(objectName); } else { // Remove the context, unless it is still recording dependencies installedServices.remove(ctx); if (ctx.dependsOnMe.size() == 0) { log.debug("removing already unregistered " + objectName + " from server"); nameToServiceMap.remove(objectName); } else { log.debug("no need to remove " + objectName + " from server"); ctx.proxy = null; } } // This context is no longer installed, but it may still exist // to record dependent services ctx.state = ServiceContext.NOTYETINSTALLED; } /** * Lookup the ServiceContext for the given serviceName * * @jmx.managed-operation */ public ServiceContext getServiceContext(ObjectName serviceName) { ServiceContext ctx = nameToServiceMap.get(serviceName); return ctx; } public void shutdown() { log.debug("Stopping " + nameToServiceMap.size() + " services"); List<ServiceContext> servicesCopy = new ArrayList<ServiceContext>(installedServices); int serviceCounter = 0; ObjectName name = null; ListIterator i = servicesCopy.listIterator(servicesCopy.size()); while (i.hasPrevious()) { ServiceContext ctx = (ServiceContext) i.previous(); name = ctx.objectName; // Go through the full stop/destroy cycle try { stop(name); } catch (Throwable e) { log.error("Could not stop mbean: " + name, e); } try { destroy(name); } catch (Throwable e) { log.error("Could not destroy mbean: " + name, e); } try { remove(name); serviceCounter++; } catch (Throwable e) { log.error("Could not remove mbean: " + name, e); } } log.debug("Stopped " + serviceCounter + " services"); } // MBeanRegistration implementation ---------------------------------------- /** * #Description of the Method * * @param server Description of Parameter * @param name Description of Parameter * @return Description of the Returned Value * @exception Exception Description of Exception */ public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { this.server = server; creator = new OldServiceCreator(server); configurator = new OldServiceConfigurator(server, this, creator); // Register the ServiceController as a running service ServiceContext sc = this.createServiceContext(name); sc.state = ServiceContext.RUNNING; log.debug("Controller MBean online"); return name == null ? OBJECT_NAME : name; } public void postRegister(Boolean registrationDone) { if (!registrationDone.booleanValue()) { log.info("Registration of ServiceController failed"); } } public void preDeregister() throws Exception { } public void postDeregister() { nameToServiceMap.clear(); installedServices.clear(); creator.shutdown(); creator = null; configurator = null; server = null; } // Package Protected --------------------------------------------- // Create a Service Context for the service, or get one if it exists synchronized ServiceContext createServiceContext(ObjectName objectName) { // If it is already there just return it if (nameToServiceMap.containsKey(objectName)) return nameToServiceMap.get(objectName); // If not create it, add it and return it ServiceContext ctx = new ServiceContext(); ctx.objectName = objectName; // we keep track of these here nameToServiceMap.put(objectName, ctx); return ctx; } void registerDependency(ObjectName needs, ObjectName used) { log.debug("recording that " + needs + " depends on " + used); ServiceContext needsCtx = createServiceContext(needs); ServiceContext usedCtx = createServiceContext(used); if (!needsCtx.iDependOn.contains(usedCtx)) { // needsCtx depends on usedCtx needsCtx.iDependOn.add(usedCtx); // UsedCtx needs to know I depend on him usedCtx.dependsOnMe.add(needsCtx); } } // Private ------------------------------------------------------- /** * Register the service context and its dependencies against the microkernel. * If the context is already registered it does nothing. * * @param ctx the ServiceContext to register * @param depends a collection of ObjectNames of services the registered service depends on */ private void register(ServiceContext ctx, Collection depends) throws Exception { if (!installedServices.contains(ctx)) installedServices.add(ctx); if (depends != null) { log.debug("adding depends in ServiceController.register: " + depends); for (Iterator i = depends.iterator(); i.hasNext();) { registerDependency(ctx.objectName, (ObjectName) i.next()); } } // Get the fancy service proxy (for the lifecycle API), if needed if (ctx.proxy == null) ctx.proxy = getServiceProxy(ctx.objectName, null); } /** * Get the Service interface through which the mbean given by objectName * will be managed. * * @param objectName * @param serviceFactory * @return The Service value * * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException */ private Service getServiceProxy(ObjectName objectName, String serviceFactory) throws ClassNotFoundException, InstantiationException, IllegalAccessException, JMException { Service service = null; ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (serviceFactory != null && serviceFactory.length() > 0) { Class clazz = loader.loadClass(serviceFactory); ServiceFactory factory = (ServiceFactory) clazz.newInstance(); service = factory.createService(server, objectName); } else { MBeanInfo info = server.getMBeanInfo(objectName); MBeanOperationInfo[] opInfo = info.getOperations(); Class[] interfaces = {Service.class}; InvocationHandler handler = new ServiceProxy(objectName, opInfo); service = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(), interfaces, handler); } return service; } /** * Sends outs controller notifications about service lifecycle events */ private void sendControllerNotification(String type, ObjectName serviceName) { Notification notification = new Notification(type, this, super.nextNotificationSequenceNumber()); notification.setUserData(serviceName); sendNotification(notification); } // Inner classes ------------------------------------------------- /** * A mapping from the Service interface method names to the corresponding * index into the ServiceProxy.hasOp array. */ private static HashMap<String, Integer> serviceOpMap = new HashMap<String, Integer>(); /** * An implementation of InvocationHandler used to proxy of the Service * interface for mbeans. It determines which of the start/stop * methods of the Service interface an mbean implements by inspecting its * MBeanOperationInfo values. Each Service interface method that has a * matching operation is forwarded to the mbean by invoking the method * through the MBeanServer object. */ public class ServiceProxy implements InvocationHandler { private boolean[] hasOp = {false, false, false, false}; private ObjectName objectName; /** Whether we have the lifecycle method */ private boolean hasJBossInternalLifecycle; /** * Go through the opInfo array and for each operation that matches on of * the Service interface methods set the corresponding hasOp array value * to true. * * @param objectName * @param opInfo */ public ServiceProxy(ObjectName objectName, MBeanOperationInfo[] opInfo) { this.objectName = objectName; for (int op = 0; op < opInfo.length; op++) { MBeanOperationInfo info = opInfo[op]; String name = info.getName(); if (name.equals(JBOSS_INTERNAL_LIFECYCLE)) { hasJBossInternalLifecycle = true; continue; } Integer opID = serviceOpMap.get(name); if (opID == null) { continue; } // Validate that is a no-arg void return type method if (info.getReturnType().equals("void") == false) { continue; } if (info.getSignature().length != 0) { continue; } hasOp[opID.intValue()] = true; } } /** * Map the method name to a Service interface method index and if the * corresponding hasOp array element is true, dispatch the method to the * mbean we are proxying. * * @param proxy * @param method * @param args * @return Always null. * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if (hasJBossInternalLifecycle) { try { server.invoke(objectName, JBOSS_INTERNAL_LIFECYCLE, new Object[] { name }, JBOSS_INTERNAL_LIFECYCLE_SIG); return null; } catch (Exception e) { throw JMXExceptionDecoder.decode(e); } } Integer opID = serviceOpMap.get(name); if (opID != null && hasOp[opID.intValue()] == true) { // deal with those pesky JMX exceptions try { String[] sig = {}; server.invoke(objectName, name, args, sig); } catch (Exception e) { throw JMXExceptionDecoder.decode(e); } } return null; } } /** * Initialize the service operation map. */ static { serviceOpMap.put("create", new Integer(0)); serviceOpMap.put("start", new Integer(1)); serviceOpMap.put("destroy", new Integer(2)); serviceOpMap.put("stop", new Integer(3)); } }