package org.marketcetera.module; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.*; import java.util.concurrent.locks.Lock; import javax.annotation.concurrent.GuardedBy; import javax.management.*; import org.marketcetera.util.log.*; import org.marketcetera.util.misc.ClassVersion; /* $License$ */ /** * The main class for the module framework. This class provides the * API to the module framework. * <p> * The {@link #init()} method should be invoked to get the module manager * to discover and instantiate providers and any module instances, after * its created. If a {@link #getConfigurationProvider()} has been setup * before the <code>init()</code> method is invoked, it will be used * to provide default property values to various module providers and * instances as they are created. * <p> * <b>Implementation Notes on Fine grained locking</b> * This class uses fine grained locks for certain operations. * Following is a summary of various fine grained locking carried out in * this class. * <ol> * <li>All data structures are synchronized on themselves to ensure * consistent behavior when used concurrently. Examples include * {@link #mModuleFactories}, {@link #mDataFlows} and {@link #mModules} </li> * <li>Module creation operations are serialized via the factory instance. * {@link ModuleFactory#getLock() lock}.</li> * <li>Module deletion operations are serialized on Module * {@link Module#getLock() write lock} * to ensure that no other operations can be performed on the module * while it's being deleted.</li> * <li>Garbage collection of auto-created modules acquires a write lock on * the module when stopping and removing it.</li> * <li>Module lifecycle operations like start and stop, acquire Module * {@link Module#getLock() write lock} when changing * the module state during the operation. The {@link Module#preStart()} & * {@link Module#preStop()} methods are invoked without acquiring any * locks on the module.</li> * <li>Data flow creation operations acquire Module * {@link Module#getLock() read lock} on the requesting and participating * modules to prevent module state changes while data flow operations * are being carried out.</li> * <li>Data flow cancellation operations acquire Module * {@link Module#getLock() read lock} on only the requesting * module to prevent module state changes while data flow operations * are being carried out.</li> * </ol> * * @author anshul@marketcetera.com * @version $Id: ModuleManager.java 16844 2014-02-24 22:51:16Z colin $ * @since 1.0.0 */ @ClassVersion("$Id: ModuleManager.java 16844 2014-02-24 22:51:16Z colin $") public final class ModuleManager { /** * Gets the singleton instance of <code>ModuleManager</code> if one * has been created. * * <p>This method makes no guarantee that a <code>ModuleManager</code> exists * or has been initialized. * * @return a <code>ModuleManager</code> value or <code>null</code> */ public static ModuleManager getInstance() { synchronized(ModuleManager.class) { return instance; } } /** * Creates an instance that uses the same classloader as this class * to load module providers. */ public ModuleManager() { this(ModuleManager.class.getClassLoader()); } /** * Creates an instance that uses the supplied classloader to * load module providers. The supplied classloader is also set * as the thread context classloader when the MXBean implementations of * the module factories and instances are invoked. * * @param inClassLoader the classloader to use for loading module * providers. */ public ModuleManager(ClassLoader inClassLoader) { mClassLoader = inClassLoader; mLoader = ServiceLoader.load(ModuleFactory.class, inClassLoader); synchronized(ModuleManager.class) { instance = this; } } /** * Returns a list of URNs of available module providers. * * @return the list of URNs of available module providers. */ public List<ModuleURN> getProviders() { ArrayList<ModuleURN> list = new ArrayList<ModuleURN>(); synchronized (mModuleFactories) { for(ModuleURN u: mModuleFactories.keySet()) { list.add(u); } } return list; } /** * Returns detailed information on a provider, given its URN. * * @param inProviderURN the provider URN * * @return the provider details. * * @throws ProviderNotFoundException if a provider with the * supplied URN does not exist. * @throws InvalidURNException if the supplied provider URN * is not a valid URN. */ public ProviderInfo getProviderInfo(ModuleURN inProviderURN) throws ProviderNotFoundException, InvalidURNException { URNUtils.validateProviderURN(inProviderURN); return getModuleFactory(inProviderURN).getProviderInfo(); } /** * Returns the URNs of available module instances. * If a module provider URN is provided, only the module instances * from that provider are returned, otherwise, all available module * instance URNs are returned back. * * The provider URN is validated to ensure that it's a valid provider URN. * If no provider with the supplied URN exists, no modules will be returned. * * @param inProviderURN the providerURN whose module * instances are requested. If null, all module instances * are returned. * * @return the list or URNs for all the modules * * @throws InvalidURNException if the supplied provider URN * is not a valid URN. */ public List<ModuleURN> getModuleInstances(ModuleURN inProviderURN) throws InvalidURNException { if (inProviderURN != null) { URNUtils.validateProviderURN(inProviderURN); } ArrayList<ModuleURN> urns = new ArrayList<ModuleURN>(); for(ModuleURN moduleURI: mModules.getAllURNs()) { if(inProviderURN == null || inProviderURN.parentOf(moduleURI)) { urns.add(moduleURI); } } return urns; } /** * Creates a module instance. An attempt to create a module instance * for a provider that only supports singleton instances will fail, if * a singleton instance already exists. * * @param inProviderURN The provider URN. The value supplied should match * the value returned by a module factory's * {@link ModuleFactory#getProviderURN()} that * is available in the system. * @param inParameters the parameters that are needed to instantiate * the module. The parameter types are verified against the types * that are advertised by the Module Factory via * {@link ModuleFactory#getParameterTypes()} * * @return the instantiated module's URN * * @throws ModuleCreationException if an attempt was made to create * multiple instances of a singleton module OR if a module with the same * URN already exists OR if wrong number / type of parameters were * supplied to create the module. * @throws InvalidURNException if the created module's URN failed URN * validation. * @throws MXBeanOperationException if there problems registering * the module's MXBean with the MBean server. * @throws ModuleException if there was an error creating a new * module instance OR if this was an auto-start module, if there were * errors starting it. See {@link #start(ModuleURN)} for details on * possible errors when starting a module. * * @see #getProviderInfo(ModuleURN) */ public ModuleURN createModule(ModuleURN inProviderURN, Object... inParameters) throws ModuleException { return createModuleImpl(inProviderURN, inParameters).getURN(); } /** * Deletes the module identified by the supplied module URN. * Singleton instances of a module cannot be deleted. The module * should not be running when this method is invoked, otherwise * the operation fails with an exception. * * @param inModuleURN the module URN, that uniquely identifies * the module being deleted. * * @throws ModuleException if a module with the * supplied URN cannot be deleted * @throws ModuleStateException if the module is not in the correct * state to be deleted * @throws InvalidURNException if the supplied module URN is * not a valid URN. * @throws ModuleNotFoundException if the module matching * the URN was not found. * @throws BeanRegistrationException if unregistration of the module's * MBean failed. */ public void deleteModule(ModuleURN inModuleURN) throws ModuleException { // URN validation already done in getModule() Module module = getModule(inModuleURN); ModuleURN providerURN = module.getURN().parent(); assert providerURN != null; if(!getModuleFactory(providerURN).isMultipleInstances()) { throw new ModuleException(new I18NBoundMessage1P( Messages.CANNOT_DELETE_SINGLETON, inModuleURN.toString())); } Lock moduleLock = module.getLock().writeLock(); //Acquire lock for module lifecycle changes and ensure //that no other operations are active on the module moduleLock.lock(); try { //Verify that the module can still be found, ie. it didn't //get deleted by another thread before we acquired the lock. getModule(inModuleURN); //And is in the right state. if(!module.getState().canBeDeleted()) { throw new ModuleStateException(new I18NBoundMessage3P( Messages.DELETE_FAILED_MODULE_STATE_INCORRECT, inModuleURN.toString(), module.getState(), ModuleState.DELETABLE_STATES.toString())); } ObjectName objectName = inModuleURN.toObjectName(); try { if(getMBeanServer().isRegistered(objectName)) { getMBeanServer().unregisterMBean(objectName); } } catch (JMException e) { throw new BeanRegistrationException(e, new I18NBoundMessage1P(Messages.MODULE_DELETE_ERROR_MXBEAN_UNREG, inModuleURN.getValue())); } mModules.remove(module.getURN()); Messages.LOG_MODULE_DELETED.info(this, inModuleURN); } finally { moduleLock.unlock(); } } /** * Returns detailed information on the module given its URN. * * @param inModuleURN the module URN * * @return the detailed module information. * * @throws ModuleNotFoundException if a module with the supplied * URN was not found * @throws InvalidURNException if the supplied URN was invalid. */ public ModuleInfo getModuleInfo(ModuleURN inModuleURN) throws ModuleNotFoundException, InvalidURNException { Module module = getModule(inModuleURN); Set<DataFlowID> initiatedFlows = mDataFlows.getInitiatedFlows(inModuleURN); Set<DataFlowID> participatingFlows = mDataFlows.getFlowsParticipating(inModuleURN); return module.getModuleInfo( initiatedFlows == null ? null : initiatedFlows.toArray(new DataFlowID[initiatedFlows.size()]), participatingFlows == null ? null : participatingFlows.toArray(new DataFlowID[participatingFlows.size()])); } /** * Starts the module instance * * @param inModuleURN the module instance URN uniquely identifying * the module that needs to be started. * * @throws ModuleStateException if the module is not in the correct * state to be started. * @throws ModuleException if {@link Module#preStart()} threw an exception * OR if there were other errors starting the module. * @throws InvalidURNException if the module URN is invalid * @throws ModuleNotFoundException if the module was not found */ public void start(ModuleURN inModuleURN) throws ModuleException { startModule(getModule(inModuleURN)); } /** * Stops a module instance. Do note that stopping a module stops * all the data flows that this module initiated. * * However, if the module is participating in data flows that it * didn't initiate, the module cannot be stopped. The user has * to manually cancel other data flows that the module is participating * in, in order to be able to stop it. * * @param inModuleURN the module instance URN uniquely identifying * the module that needs to be stopped. * * @throws ModuleNotFoundException if a module with the supplied * URN was not found * @throws InvalidURNException if the supplied module URN is invalid. * @throws ModuleStateException if the module is not in the correct state * to be stopped. * @throws DataFlowException if the module is participating in data flows * that it didn't initiate. * @throws ModuleException if {@link Module#preStop()} threw an exception * or if there were other errors stopping the module. */ public void stop(ModuleURN inModuleURN) throws ModuleException { stopModule(getModule(inModuleURN)); } /** * Creates a data flow between the modules identified by * the supplied requests. Each data request should uniquely identify * a module via its URN attribute. Its an error if none or * multiple modules match the URN. * * For each matched module, a request is initiated supplying it the * data request. * * The system will automatically append the sink module to the * data flow if the last module identified by the request is * capable of emitting data and if the sink has not been already * specified as the last module in the pipeline. * * Invoking this method is the same as invoking * <code>createDataFlow(requests,true);</code> * * @param inRequests the request instances * * @return the ID identifying the data flow. * * @throws ModuleException if any of the requested modules could * not be found, or instantiated or configured. Or if any of the * modules were not capable of emitting or receiving data as * requested. Or if any of the modules didn't understand the * request parameters or were unable to emit data as requested. * * @see #createDataFlow(DataRequest[], boolean) */ public DataFlowID createDataFlow(DataRequest[] inRequests) throws ModuleException { return createDataFlow(inRequests,true); } /** * Creates a requested connection between the modules identified by * the supplied requests. Each data request should uniquely identify * a module via its URN attribute. Its an error if none or * multiple modules match the URN. * * For each matched module, a request is initiated supplying it the * data request. * * Each of the modules specified in the request should already be * started for the request to succeed. This request will fail * if any of the modules specified in the request are not started * and are not * {@link org.marketcetera.module.Module#isAutoStart() auto-start}. * * @param inRequests the request instances * @param inAppendSink if the sink module should be automatically * appended to the tail end of the data flow. * * @return the ID identifying the data flow. * * @throws DataFlowException if the data request wasn't specified correctly * OR if a non-emitter module was requested to participate as an emitter OR * if a non-receiver module was requested to participate as a receiver. * @throws ModuleStateException if the participating modules were not in * the correct state to be able to participate in the data flow. * @throws RequestDataException if any of the participating modules failed * when * {@link DataEmitter#requestData(DataRequest, DataEmitterSupport)} initiating} * the request. * @throws ModuleNotFoundException if a module instance corresponding to * the specified module URN could not be found. * @throws ModuleException if there were other errors setting up * data flow including errors instantiating any auto-instantiated modules * in the data flow. */ public DataFlowID createDataFlow(DataRequest[] inRequests, boolean inAppendSink) throws ModuleException { return createDataFlow(inRequests,inAppendSink, null); } /** * Cancels the data flow identified by the supplied data flow ID. * Do note that data flows that have been initiated by * {@link #createDataFlow(DataRequest[])} can be canceled by this * method. * * Specifically, data flows created by modules via * {@link org.marketcetera.module.DataFlowSupport#createDataFlow(DataRequest[])} * cannot be canceled by this method. They can only be canceled by the * module that initiated the data flow request. * * @param inFlowID the data flow ID. * * * @throws ModuleStateException If the requesting module is not in the * correct state to be requesting cancellation of data flows. * @throws DataFlowNotFoundException if the data flow corresponding * to the supplied ID wasn't found. * @throws DataFlowException if the data flow is in the process of * being canceled by another operation. */ public void cancel(DataFlowID inFlowID) throws ModuleStateException, DataFlowException { cancel(inFlowID, null); } /** * Returns all active the data flows. * * @param inIncludeModuleCreated if the data flows created by * the module should be included in the returned list. * * @return the list of IDs of all data flows in the system. */ public List<DataFlowID> getDataFlows(boolean inIncludeModuleCreated) { return mDataFlows.getDataFlows(inIncludeModuleCreated); } /** * Returns details of data flow given the data flow ID. * Only active data flows can be queried via this mechanism. * * @param inFlowID the data flow ID * * @return the data flow details * * @throws DataFlowNotFoundException if the data flow, specified by * the ID, could not be found. */ public DataFlowInfo getDataFlowInfo(DataFlowID inFlowID) throws DataFlowNotFoundException { DataFlow flow = mDataFlows.get(inFlowID); if (flow != null) { return flow.toDataFlowInfo(); } else { throw new DataFlowNotFoundException(new I18NBoundMessage1P( Messages.DATA_FLOW_NOT_FOUND, inFlowID.getValue())); } } /** * Returns the historical record of data flows that are not active * anymore. The number of records returned are determined by the * current value returned by {@link #getMaxFlowHistory()}. * * @return historical record of data flows that are not active * anymore. */ public List<DataFlowInfo> getDataFlowHistory() { synchronized (mFlowHistory) { return new ArrayList<DataFlowInfo>(mFlowHistory); } } /** * Returns the module configuration provider instance being used * for providing default values for the module factory / instance * configuration attributes. * * @return the module configuration provider instance. */ public ModuleConfigurationProvider getConfigurationProvider() { return mConfigurationProvider; } /** * Discovers all the module implementations and instantiates * all the singleton instances. * <p> * This method should only be invoked once for an instance. Multiple * invocations of the method after the first one have undefined behavior. * * @throws ModuleException If there were errors initializing * the module framework. */ public void init() throws ModuleException { //Register itself with the platform MBean server synchronized (mOperationsLock) { boolean failed = true; try { for(ModuleFactory factory: mLoader) { initialize(factory); } SLF4JLoggerProxy.info(this, mModules.toString()); // Supply this reference to the Sink module for sink listening to work ((SinkModule)getModule(SinkModuleFactory.INSTANCE_URN)).setManager(this); registerMXBean(new ModuleManagerMXBeanImpl(this), new ObjectName(MODULE_MBEAN_NAME)); failed = false; } catch (ServiceConfigurationError e) { throw new ModuleException(e, Messages.MODULE_CONFIGURATION_ERROR); } catch (JMException e) { throw new BeanRegistrationException(e,new I18NBoundMessage1P( Messages.BEAN_REGISTRATION_ERROR,MODULE_MBEAN_NAME)); } finally { if(failed) { try { stop(); } catch(Exception e) { Messages.ERROR_CLEANING_UP_INIT_FAILURE.error(this, e); } } } } } /** * Refreshes the set of module providers. Any new module provider * jars that have been made available in the class path will be * discovered and processed as a result. * * The existing module providers will remain unchanged. * * Also {@link #refresh() refreshes} the module configuration * provider, if one has been provided. * * @throws ModuleException if there were errors initializing * newly discovered factories. */ public void refresh() throws ModuleException { try { synchronized (mOperationsLock) { boolean doRefresh = true; if(mRefreshListener != null) { doRefresh = mRefreshListener.refresh(); } if (doRefresh) { mLoader.reload(); //Refresh the configuration provider if(getConfigurationProvider() != null) { getConfigurationProvider().refresh(); } for(ModuleFactory factory: mLoader) { initialize(factory); } } } } catch (ServiceConfigurationError e) { throw new ModuleException(e, Messages.MODULE_CONFIGURATION_ERROR); } catch (IOException e) { throw new ModuleException(e, Messages.ERROR_REFRESH); } } /** * Sets the refresh listener that intercepts the {@link #refresh()} * invocations. Only one refresh listener can be setup. If a * refresh listener is already setup when this method is invoked, * an exception is thrown. * * @param inRefreshListener the refresh listener instance * * @throws ModuleException if a refresh listener is already setup. */ public void setRefreshListener(RefreshListener inRefreshListener) throws ModuleException { synchronized (mOperationsLock) { if (mRefreshListener == null) { mRefreshListener = inRefreshListener; } else { throw new ModuleException(new I18NBoundMessage1P( Messages.REFRESH_LISTENER_ALREADY_SETUP, mRefreshListener.getClass().getName())); } } } /** * Adds a sink listener. * * @param inListener the sink listener */ public void addSinkListener(SinkDataListener inListener) { synchronized (mSinkListenerLock) { HashSet<SinkDataListener> listeners = new HashSet<SinkDataListener>( Arrays.asList(mSinkListeners)); listeners.add(inListener); mSinkListeners = listeners.toArray( new SinkDataListener[listeners.size()]); } } /** * Removes a sink listener. * * @param inListener the sink listener * * @return true if the listener was found in the list of listeners. */ public boolean removeSinkListener(SinkDataListener inListener) { synchronized (mSinkListenerLock) { HashSet<SinkDataListener> listeners = new HashSet<SinkDataListener>( Arrays.asList(mSinkListeners)); boolean wasFound = listeners.remove(inListener); mSinkListeners = listeners.toArray( new SinkDataListener[listeners.size()]); return wasFound; } } /** * Initializes the module configuration provider for the module framework. * <p> * This method should be invoked before {@link #init()} is invoked. * Invocation of this method after {@link #init()} has been invoked * has undefined behavior. * * @param inConfigurationProvider the module configuration provider instance * to use for this framework */ public void setConfigurationProvider( ModuleConfigurationProvider inConfigurationProvider) { mConfigurationProvider = inConfigurationProvider; } /** * Stops the module manager and all the data flow activities. * This method, stops all the non-module initiated data flows first, * it then stops all the modules that have initiated data flows, * finally it stops all the running modules. * * @throws ModuleException if there were errors stopping the module manager. */ public void stop() throws ModuleException { Messages.LOG_STOPPING_MODULE_MANAGER.info(this); try { synchronized(mOperationsLock) { ModuleURN moduleURN; //find out all the data flows that are not initiated by //modules List<DataFlowID> flows = mDataFlows.getDataFlows(false); for(DataFlowID flowID: flows) { cancel(flowID); } //Now find out all the data flows that are initiated by modules //and stop the initiating modules flows = mDataFlows.getDataFlows(true); //All the returned flows should be module initiated as the //non-module initiated flows have been cancelled. for(DataFlowID flowID: flows) { DataFlow flow = mDataFlows.get(flowID); //We might not get a data flow for an ID if a module //that spawned multiple flows is stopped in a previous //iteration. if(flow == null) { continue; } //Do note that this may fail if the requesting module //is participating in data flows that it didn't initiate. //If that happens, this method will fail, the user will //need to resolve the dependency manually and retry stop. moduleURN = flow.getRequesterURN(); Module m = getModule(moduleURN); if(m.getState().isStarted()) { stop(moduleURN); } } //Get all the modules and stop them, if they are not stopped, //stop them. ModuleURN [] urns = mModules.getAllURNs(); for(ModuleURN urn: urns) { Module m = mModules.get(urn); if(m.getState().isStarted() && //Skip the sink module as it cannot be stopped. !SinkModuleFactory.INSTANCE_URN.equals(urn)) { stop(urn); } } //Clean up all the mbeans. ObjectName name = new ObjectName(MODULE_MBEAN_NAME); //unregister the module manager unregister(name); //unregister all instances for(ModuleURN urn: getModuleInstances(null)) { unregister(urn); } //unregister all providers for(ModuleURN urn: getProviders()) { unregister(urn); } } } catch (ModuleException e) { Messages.LOG_STOP_FAILURE.error(this,e); throw e; } catch (JMException e) { throw new ModuleException(e, Messages.BEAN_UNREGISTRATION_ERROR); } } /** * Sets the MBean server to use for all JMX operations. This method * should be invoked prior to invoking {@link #init()}. The behavior * of the module manager is not defined if this method is invoked * after the module manager has been initialized. * <p> * By default the platform MBean server is used for all JMX operations. * * @param inMBeanServer The MBean server to use for all operations. */ public void setMBeanServer(MBeanServer inMBeanServer) { mMBeanServer = inMBeanServer; } /** * Get the classLoader value. * * @return a <code>ClassLoader</code> value */ public ClassLoader getClassLoader() { return mClassLoader; } /** * Creates a module instance. An attempt to create a module instance * for a provider that only supports singleton instances will fail. * All singleton instances are created when ModuleManager is * initialized. * <p> * This method can only instantiate modules whose creation requires * only those parameter types as supported by * {@link org.marketcetera.module.StringToTypeConverter}. If an * attempt is made to create a module supplying string value for * an unsupported type, the module creation will fail with a type * mismatch error. * <p> * Parameters of any type can be used to instantiate modules via * {@link #createModule(ModuleURN, Object[])}. However, this API * is only available for local invocation as JMX doesn't support * remoting of any random data type. * * * @param providerURN The provider URN. The value supplied should match * the value returned by a module factory's * {@link ModuleFactory#getProviderURN()} that * is available in the system. * @param parameterList the comma separated list of parameters that * are needed to instantiate the module. The string parameters are * converted to object types based on type values returned by * {@link ModuleFactory#getParameterTypes()}. If any of the types * returned by {@link ModuleFactory#getParameterTypes()} are not * supported by {@link org.marketcetera.module.StringToTypeConverter}, * this method will fail. * * @return the instantiated module's URN * * @throws ModuleException if there were errors creating the module * * @see #getProviderInfo(ModuleURN) */ ModuleURN createModuleJMX(ModuleURN providerURN, String parameterList) throws ModuleException { URNUtils.validateProviderURN(providerURN); ModuleFactory factory = getModuleFactory(providerURN); //Attempt conversion of the parameters //Leave error handling to the overloaded method invoked at the end. //Split parameters using , as a delimiter String[] parameters = null; if(parameterList != null) { parameters = parameterList.split(","); //$NON-NLS-1$ } Object[] params = null; Class<?>[] parameterTypes = factory.getParameterTypes(); if(parameterTypes != null && parameterTypes.length != 0 && parameters != null && parameters.length > 0) { //Ignore extra parameters params = new Object[Math.min(parameters.length, parameterTypes.length)]; for(int i= 0; i < params.length; i++) { Class<?> type = parameterTypes[i]; if(StringToTypeConverter.isSupported(type)) { //convert parameters whose conversion is supported try { params[i] = StringToTypeConverter.convert(type, parameters[i]); } catch (IllegalArgumentException e) { throw new MXBeanOperationException(e, new I18NBoundMessage2P( Messages.CANNOT_CREATE_MODULE_PARAM_CONVERT_ERROR, providerURN.toString(),i)); } } else { //ignore conversion for parameters that are not supported //the subsequent API performing parameter type checks //will throw the failure. params[i] = parameters[i]; } } } return createModule(providerURN, params); } /** * Creates a new data flow. * * @param inRequests the data flow requests. * @param inAppendSink if the sink module should be * automatically appended to the data flow * @param inRequester the module requesting this data flow. null, * if the data flow was not requested by a module. * * @return unique ID identifying the data flow * * @throws DataFlowException if the data request wasn't specified correctly * OR if a non-emitter module was requested to participate as an emitter OR * if a non-receiver module was requested to participate as a receiver. * @throws ModuleStateException if the participating modules were not in * the correct state to be able to participate in the data flow. * @throws RequestDataException if any of the participating modules failed * when * {@link DataEmitter#requestData(DataRequest, DataEmitterSupport)} initiating} * the request. * @throws ModuleNotFoundException if a module instance corresponding to * the specified module URN could not be found. * @throws ModuleException if there were other errors setting up * data flow including errors instantiating any auto-instantiated modules * in the data flow. */ DataFlowID createDataFlow(DataRequest[] inRequests, boolean inAppendSink, Module inRequester) throws ModuleException { //verify that there are enough modules to create a data flow. if(inRequests == null || inRequests.length < (inAppendSink ? 1 : 2)) { throw new DataFlowException(new I18NBoundMessage1P( Messages.DATA_REQUEST_TOO_SHORT, inRequests == null ? 0 : inRequests.length)); } Lock requesterLock = null; boolean failed = true; Module[] modules = null; try { // verify that the requester is in the right state to // be requesting data flows. if(inRequester != null) { //acquire the requester lock to prevent state changes to //it while the data flow is being setup. requesterLock = inRequester.getLock().readLock(); requesterLock.lock(); ModuleState state = inRequester.getState(); if (!(state.canRequestFlows())) { throw new ModuleStateException(new I18NBoundMessage3P( Messages.DATAFLOW_FAILED_REQ_MODULE_STATE_INCORRECT, inRequester.getURN().toString(), state, ModuleState.REQUEST_FLOW_STATES.toString())); } } //Find and lock modules corresponding to each data request modules = findAndLockModules(inRequests, inRequester); //Append the sink module if requested and possible if(inAppendSink && // Last module is not the sink module !(SinkModuleFactory.INSTANCE_URN.equals( modules[modules.length - 1].getURN())) && // the last module is capable of emitting data modules[modules.length - 1] instanceof DataEmitter) { //Add sink module to the tail end of the pipeline. modules = Arrays.copyOf(modules, modules.length + 1); modules[modules.length - 1] = getModule( SinkModuleFactory.INSTANCE_URN); //Acquire the lock on the sink module, like all other modules modules[modules.length - 1].getLock().readLock().lock(); //Also add a data request to append the sink module inRequests = Arrays.copyOf(inRequests, inRequests.length + 1); inRequests[inRequests.length - 1] = new DataRequest( SinkModuleFactory.INSTANCE_URN); } if(inRequests.length < 2) { throw new DataFlowException(new I18NBoundMessage1P( Messages.DATA_REQUEST_TOO_SHORT, inRequests.length)); } // Iterate through the list of module verifying that they can // handle data flows and are started. for(int i = 0; i < modules.length; i++) { Module module = modules[i]; //verify that all modules except the last one can emit data if((i < (modules.length - 1)) && !(module instanceof DataEmitter)) { throw new DataFlowException(new I18NBoundMessage1P( Messages.MODULE_NOT_EMITTER, module.getURN().toString())); } //verify that all modules except the first one can receive data if(i > 0 && !(module instanceof DataReceiver)) { throw new DataFlowException(new I18NBoundMessage1P( Messages.MODULE_NOT_RECEIVER, module.getURN().toString())); } //Check if the modules are in the state wherein they can //participate in data flows. Skip the check for the requester //as it's already been checked before. if(inRequester != module && (!module.getState().canParticipateFlows())) { throw new ModuleStateException(new I18NBoundMessage3P( Messages.DATAFLOW_FAILED_PCPT_MODULE_STATE_INCORRECT, module.getURN().toString(), module.getState(), ModuleState.PARTICIPATE_FLOW_STATES.toString())); } } // Start going backwards through the modules array plumbing them // plumbing the first module last, ensures that the rest of the data // pipeline is ready to receive data once the emitter starts emitting // data DataFlow flow = new DataFlow(this, inRequester == null ? null : inRequester.getURN(), inRequests, modules); DataFlowID id = flow.getFlowID(); //Add the flow right away, to allow any concurrent flow //cancellations, that happen while the data flow is being //initialized, to not fail as they are not able to find the data //flow. mDataFlows.addFlow(flow); try { flow.initFlow(); failed = false; } finally { if(failed) { //Remove the flow if it didn't initialize mDataFlows.remove(id); } } return id; } finally { if(requesterLock != null) { requesterLock.unlock(); } if(modules != null) { for(Module module: modules) { module.getLock().readLock().unlock(); } } //garbage collect any auto-created modules, if data flow creation //failed. if(failed && modules != null) { for(Module m: modules) { removeIfOrphaned(m, null); } } } } /** * Cancels the data flow. * * @param inFlowID the data flow ID of the data flow that needs * to be canceled. * @param inRequester the module requesting the cancellation, null if * the cancellation is not requested by a module. * * @throws ModuleStateException If the requesting module is not in the * correct state to be requesting cancellation of data flows. * @throws DataFlowNotFoundException if the data flow corresponding * to the supplied ID wasn't found. * @throws DataFlowException if the data flow is in the process of * being canceled by another operation. */ void cancel(DataFlowID inFlowID, Module inRequester) throws ModuleStateException, DataFlowException { DataFlow flow; Lock requesterLock = null; DataFlowInfo dataFlowInfo = null; try { if(inRequester != null) { requesterLock = inRequester.getLock().readLock(); //acquire requester lock to prevent requester state changes //while this operation is running. requesterLock.lock(); ModuleState state = inRequester.getState(); if (!state.canCancelFlows()) { throw new ModuleStateException(new I18NBoundMessage4P( Messages.CANCEL_FAILED_MODULE_STATE_INCORRECT, inFlowID.getValue(), inRequester.getURN().toString(), state, ModuleState.CANCEL_FLOW_STATES.toString())); } } flow = mDataFlows.get(inFlowID); if(flow == null) { throw new DataFlowNotFoundException(new I18NBoundMessage1P( Messages.DATA_FLOW_NOT_FOUND, inFlowID.getValue())); } //No need to acquire locks when cancelling flows as module //state changes are blocked until the module is participating //in the flow flow.cancel(inRequester == null ? null : inRequester.getURN()); dataFlowInfo = flow.toDataFlowInfo(); //Remove the data flow after the flow is canceled, so that //the participating modules cannot be stopped until flow //cancellation is complete mDataFlows.remove(inFlowID); addToFlowHistory(dataFlowInfo); } finally { if(requesterLock != null) { requesterLock.unlock(); } } //figure out if there are any auto-created modules //and if they are not participating in any data flows //delete them for(DataFlowStep step: dataFlowInfo.getFlowSteps()) { removeIfOrphaned(step.getModuleURN(), dataFlowInfo.getFlowID()); } } /** * Receives sink data from the Data Sink Module * * @param inFlowID the data flow ID of the data flow that * delivered the data. * @param inData the data */ void receiveSinkData(DataFlowID inFlowID, Object inData) { for(SinkDataListener listener: mSinkListeners) { try { listener.receivedData(inFlowID, inData); } catch (Throwable e) { Messages.LOG_SINK_LISTENER_RECEIVE_ERROR.error(this, e); } } } /** * The maximum number of data flow history records to retain. * The default value is set to {@link #DEFAULT_MAX_FLOW_HISTORY}. * * @return maximum number of data flow history records to retain. */ int getMaxFlowHistory() { return mMaxFlowHistory; } /** * Set the maximum number of data flow history records to retain. * If the value is reset to a value lower than the current value, * the older history records are pruned to bring down the size * of the historical records to the new value. * * @param inMaxFlowHistory the maximum number of data flow history * records to retain. */ void setMaxFlowHistory(int inMaxFlowHistory) { mMaxFlowHistory = inMaxFlowHistory; //re-size the history records. addToFlowHistory(null); } /** * Returns the MBean server to use for all JMX operations. * * @return the MBean server to use for all JMX operations. */ private MBeanServer getMBeanServer() { return mMBeanServer; } /** * Creates a module instance after performing module factory checks. * Ensures that multiple instances for singleton modules do not get * created. Verifies that the correct number and types of parameters * are provided to create the module. * * @param inProviderURN the module provider URN * @param inParameters the parameters to create the module * * @return the created module instance * * @throws ModuleCreationException if an attempt was made to create * multiple instances of a singleton module OR if a module with the same * URN already exists OR if wrong number / type of parameters were * supplied to create the module. * @throws InvalidURNException if the created module's URN failed URN * validation. * @throws MXBeanOperationException if there problems registering * the module's MXBean with the MBean server. * @throws ModuleException if there was an error creating a new * module instance OR if this was an auto-start module, if there were * errors starting it. */ private Module createModuleImpl(ModuleURN inProviderURN, Object... inParameters) throws ModuleException { //validate the URN URNUtils.validateProviderURN(inProviderURN); Module module; //find the provider factory ModuleFactory factory = getModuleFactory(inProviderURN); //check if this module supports multiple instances //and if it doesn't verify that no other instances exist //This is a check to fail early. Another thread-safe check is //performed later if(!factory.isMultipleInstances()) { List<ModuleURN> urns = getModuleInstances(inProviderURN); if(!urns.isEmpty()) { throw new ModuleCreationException(new I18NBoundMessage2P( Messages.CANNOT_CREATE_SINGLETON, inProviderURN.toString(), urns.get(0).toString())); } } //Verify if the parameter types match the advertised types Class<?>[] paramTypes = factory.getParameterTypes(); //deal with nulls an empty arrays. if( // No parameters are needed but some are provided (paramTypes.length == 0 && inParameters != null && inParameters.length != 0) || // OR parameters are needed but the right number are not // provided (paramTypes.length != 0 && (inParameters == null || paramTypes.length != inParameters.length))) { throw new ModuleCreationException(new I18NBoundMessage3P( Messages.CANNOT_CREATE_MODULE_WRONG_PARAM_NUM, inProviderURN.toString(), paramTypes.length, inParameters == null ? 0 : inParameters.length)); } //Verify if the correct parameter types have been supplied int i = 0; for(Class<?> c: paramTypes) { if(inParameters[i] != null && !c.isInstance(inParameters[i]) && !isPrimitiveMatch(c, inParameters[i])) { throw new ModuleCreationException(new I18NBoundMessage4P( Messages.CANNOT_CREATE_MODULE_WRONG_PARAM_TYPE, inProviderURN.toString(), i,c.getName(), inParameters[i].getClass().getName())); } i++; } //create the module module = createModule(factory, inParameters); return module; } /** * Returns true if the supplied class is of primitive type * and the supplied parameter is of the same type as the * boxed primitive type. * * @param inClass the primitive class * @param inParameter the parameter * * @return if the supplied parameter is of the boxed primitive type. */ private boolean isPrimitiveMatch(Class<?> inClass, Object inParameter) { if(inClass.isPrimitive()) { if(Boolean.TYPE.equals(inClass)) { return Boolean.class.isInstance(inParameter); } else if (Byte.TYPE.equals(inClass)) { return Byte.class.isInstance(inParameter); } else if (Character.TYPE.equals(inClass)) { return Character.class.isInstance(inParameter); } else if (Short.TYPE.equals(inClass)) { return Short.class.isInstance(inParameter); } else if (Integer.TYPE.equals(inClass)) { return Integer.class.isInstance(inParameter); } else if (Float.TYPE.equals(inClass)) { return Float.class.isInstance(inParameter); } else if (Long.TYPE.equals(inClass)) { return Long.class.isInstance(inParameter); } else if (Double.TYPE.equals(inClass)) { return Double.class.isInstance(inParameter); } } return false; } /** * Adds the flow info to flow history. Prunes the older * records from the history to ensure that size of the history * records does not exceed {@link #getMaxFlowHistory()}. * * If the data flow has any participating modules that were * auto-created, if they are no longer participating in any data flow, they * are deleted. * * @param inDataFlowInfo data flow info to add to the history. * null, if there are no records to add. */ private void addToFlowHistory(DataFlowInfo inDataFlowInfo) { synchronized (mFlowHistory) { while (mFlowHistory.size() > mMaxFlowHistory) { mFlowHistory.removeLast(); } if (inDataFlowInfo != null) { mFlowHistory.addFirst(inDataFlowInfo); } } } /** * This method is invoked when canceling a data flow. * This method will check if the module with the supplied URN is * auto-created. If it is and is not participating in any data flows, * it will delete the module. * * @param inModuleURN the module URN, cannot be null. * @param inFlowID the data flowID, can be null. * */ private void removeIfOrphaned(ModuleURN inModuleURN, DataFlowID inFlowID) { Module m = mModules.get(inModuleURN); removeIfOrphaned(m, inFlowID); } /** * This method is invoked when canceling a data flow. * This method will check if the module with the supplied module is * auto-created. If it is and is not participating in any data flows, * it will delete the supplied module. * * @param inModule the module, if null, this method does nothing. * @param inFlowID the data flowID, can be null. * */ private void removeIfOrphaned(Module inModule, DataFlowID inFlowID) { if(inModule != null && inModule.isAutoCreated()) { //delete the module if its not participating //in any data flows Lock moduleLock = inModule.getLock().writeLock(); //Acquire write lock on the module to ensure that its state //flow participation doesn't change as we figure out if //we need to delete it and stop & delete it. moduleLock.lock(); try { //verify that the module still exists, it case it got removed //before we acquired the lock if(!mModules.has(inModule.getURN())) { return; } final Set<DataFlowID> flows = mDataFlows.getFlowsParticipatingNotInitiated(inModule.getURN()); if(flows == null || flows.isEmpty()) { Messages.LOG_DELETE_AUTO_CREATED_MODULE.info( this, inModule.getURN(), inFlowID); try { stopModule(inModule); deleteModule(inModule.getURN()); } catch (Exception e) { Messages.LOG_DELETE_AUTO_CREATED_MODULE_FAIL.warn(this, e, inModule.getURN()); } } } finally { moduleLock.unlock(); } } } /** * Unregisters the mbean for the factory / instance * with the supplied URN. * * @param urn the module factory / instance URN. * * @throws JMException if there were errors. * @throws ModuleException if there were errors converting module URN * to object name. */ private void unregister(ModuleURN urn) throws JMException, ModuleException { unregister(urn.toObjectName()); } /** * Unregisters the mbean with the supplied object name from * the mbean server, if one is registered. * * @param inName the mbean object name. * * @throws JMException if there was a failure. */ private void unregister(ObjectName inName) throws JMException { if (getMBeanServer().isRegistered(inName)) { getMBeanServer().unregisterMBean(inName); } } /** * Retrieves the module factory for the supplied URN. * * @param inUrn the provider URN. The URN should be validated. This method * doesn't validate the URN and its behavior is unspecified if the supplied * URN is not validated. * * @return the module factory instance. * * @throws ProviderNotFoundException if the module factory was not found. */ private ModuleFactory getModuleFactory(ModuleURN inUrn) throws ProviderNotFoundException { ModuleFactory factory; factory = findFactoryWithURN(inUrn); if(factory == null) { throw new ProviderNotFoundException(new I18NBoundMessage1P( Messages.PROVIDER_NOT_FOUND, inUrn.toString())); } return factory; } /** * Finds the factory with the supplied provider URN. Returns null * if a factory with the supplied provider URN is not found. * * @param inUrn the provider URN. The URN should be validated. This method * doesn't validate the URN and its behavior is unspecified if the supplied * URN is not validated. * * @return the module factory instance, if one having the supplied * provider URN was found, null otherwise. */ private ModuleFactory findFactoryWithURN(ModuleURN inUrn) { synchronized (mModuleFactories) { return mModuleFactories.get(inUrn); } } /** * Finds the module, given its instance URN. * * @param inModuleURN the module instance URN. The URN should already * be validated, this method does not validate the supplied URN. * * @return the module instance * * @throws InvalidURNException if the module URN is invalid * @throws ModuleNotFoundException if the module was not found */ private Module getModule(ModuleURN inModuleURN) throws InvalidURNException, ModuleNotFoundException { URNUtils.validateInstanceURN(inModuleURN); Module module = mModules.get(inModuleURN); if(module == null) { throw new ModuleNotFoundException(new I18NBoundMessage1P( Messages.MODULE_NOT_FOUND, inModuleURN.toString())); } return module; } /** * Stops the provided module instance. * * @param inModule the module instance that needs to be stopped. * * @throws ModuleStateException if the module is not in the correct state * to be stopped. * @throws DataFlowException if the module is participating in data flows * that it didn't initiate. * @throws ModuleException if {@link Module#preStop()} threw an exception * OR if there were other errors stopping the module. */ private void stopModule(Module inModule) throws ModuleException { Set<DataFlowID> initiated; Lock moduleLock = inModule.getLock().writeLock(); //acquire the lock for state changes. moduleLock.lock(); try { //verify that the module is running ModuleState state = inModule.getState(); if(!state.canBeStopped()) { throw new ModuleStateException(new I18NBoundMessage3P( Messages.MODULE_NOT_STOPPED_STATE_INCORRECT, inModule.getURN().toString(), state, ModuleState.STOPPABLE_STATES.toString())); } //cannot stop the sink module if(inModule.getURN().equals(SinkModuleFactory.INSTANCE_URN)) { throw new ModuleException(Messages.CANNOT_STOP_SINK_MODULE); } //verify that the module is not participating in flows that it // didn't initiate Set<DataFlowID> participating = mDataFlows. getFlowsParticipatingNotInitiated(inModule.getURN()); if(participating != null && (!participating.isEmpty())) { throw new DataFlowException(new I18NBoundMessage2P( Messages.CANNOT_STOP_MODULE_DATAFLOWS, inModule.getURN().toString(), participating.toString())); } inModule.setState(ModuleState.STOPPING); } finally { moduleLock.unlock(); } boolean stopSucceeded = false; try { inModule.preStop(); //cancel initiated flows initiated = mDataFlows.getInitiatedFlows(inModule.getURN()); if (initiated != null) { for(DataFlowID flowID: initiated) { cancel(flowID); } } stopSucceeded = true; Messages.LOG_MODULE_STOPPED.info(this, inModule.getURN()); } catch (ModuleException e) { inModule.setLastStopFailure(e.getLocalizedDetail()); Messages.LOG_STOP_MODULE_FAILED.warn(this, e, inModule.getURN()); throw e; } finally { //Acquire the lock for all state changes. moduleLock.lock(); try { if(stopSucceeded) { inModule.setState(ModuleState.STOPPED); inModule.setLastStopFailure(null); } else { inModule.setState(ModuleState.STOP_FAILED); } } finally { moduleLock.unlock(); } } } /** * Find module instances matching the specified requests and acquire their * read locks before they are returned. No read locks are acquired if this * method fails. * * @param inRequests the requests * @param inRequester the module instance requesting the data flow. * null, if the data flow wasn't requested by a module. * * @return the array of module instances that should participate * in this data flow. * * @throws InvalidURNException if invalid module instance URNs * were specified in any of the requests * * @throws ModuleNotFoundException if module corresponding to URNs, * specified in the requests, were not found. */ private Module[] findAndLockModules(DataRequest[] inRequests, Module inRequester) throws ModuleException { Module[] modules = new Module[inRequests.length]; int i = 0; for(DataRequest request:inRequests) { modules[i++] = findModule(URNUtils.processURN( inRequester == null ? null : inRequester.getURN(), request.getRequestURN())); } //lock all the modules before returning. for(Module m: modules) { m.getLock().readLock().lock(); } return modules; } /** * Finds the module corresponding to the specified URN. * The URN may have some of its elements missing. The system * will find a matching module such that all the specified elements * of the specified URN match the module instance URN. * * If multiple modules match the specified URN, this operation fails. * * @param inModuleURN the module URN * * @return the Module instance corresponding to the * specified URI * * @throws InvalidURNException if the specified URN was invalid * @throws ModuleNotFoundException if a module matching the * URI wasn't found. */ private Module findModule(ModuleURN inModuleURN) throws ModuleException { Module m = mModules.search(inModuleURN); if(m == null) { //Check if the module can be auto-instantiated. if(inModuleURN.instanceURN()) { ModuleURN parent = inModuleURN.parent(); ModuleFactory factory = findFactoryWithURN(parent); if(factory != null && factory.isAutoInstantiate()) { URNUtils.validateInstanceURN(inModuleURN); Module module = createModuleImpl(parent, inModuleURN); module.setAutoCreated(true); return module; } } throw new ModuleNotFoundException(new I18NBoundMessage1P( Messages.MODULE_NOT_FOUND, inModuleURN.toString())); } return m; } /** * Initializes the module factory. This method is not re-entrant, * its caller should guarantee that this method is not invoked * from multiple threads simultaneously. * * @param inFactory the factory to initialize * * @throws ModuleException if there was an error initializing * the factory */ private void initialize(ModuleFactory inFactory) throws ModuleException { Messages.LOG_INIT_FACTORY.info(this, inFactory.getClass().getName()); //Validate the inFactory ModuleURN urn = inFactory.getProviderURN(); URNUtils.validateProviderURN(urn); if(inFactory.isMultipleInstances() && inFactory.isAutoInstantiate()) { //verify that it only needs ModuleURN parameter to create //new instances Class<?>[] list = inFactory.getParameterTypes(); if(list == null || list.length != 1 || (!ModuleURN.class.equals(list[0]))) { throw new ModuleException(new I18NBoundMessage1P( Messages.INCORRECT_FACTORY_AUTO_INSTANTIATE, urn.toString())); } } //Add the factory to the table, if not already there synchronized (mModuleFactories) { if(!mModuleFactories.containsKey(urn)) { mModuleFactories.put(urn, inFactory); } else { //Ignore the factory if its already there. //This may happen during a refresh. Messages.LOG_INIT_FACTORY_IGNORE.info(this, inFactory.getClass().getName()); return; } } //Figure out if it has a MXBean interface if(isMXBean(inFactory)) { boolean mBeanOpsFailed = true; try { ObjectName objectName = registerMXBean(urn, inFactory); Messages.LOG_REGISTERED_FACTORY_BEAN.info(this, urn, objectName); //Configure the default values, if available boolean initDefaultValueFailed = true; try { initializeDefaultValues(urn, objectName); initDefaultValueFailed = false; } finally { if(initDefaultValueFailed) { try { unregister(objectName); } catch (JMException e) { SLF4JLoggerProxy.debug(this, e, "Error unregistering MBean {} on init default value failure", //$NON-NLS-1$ objectName); } } } mBeanOpsFailed = false; } finally { if(mBeanOpsFailed) { //remove the factory from the list initialized factories synchronized (mModuleFactories) { mModuleFactories.remove(urn); } } } } //Figure out if its a singleton, instantiate the singleton instance if(!inFactory.isMultipleInstances()) { // Test if the module can be instantiated without any parameters if(inFactory.getParameterTypes() == null || inFactory.getParameterTypes().length == 0) { createModule(inFactory); } } } /** * Registers the supplied module factory / instance with the * MBean server. * * @param inURN the module factory / instance URN. The MXBean's * object name is derived from the URI. * @param inMXBean the module factory / instance. * * @return the objectName of the registered bean * * @throws MXBeanOperationException if there were errors * registering the bean. */ private ObjectName registerMXBean(ModuleURN inURN, Object inMXBean) throws MXBeanOperationException { ObjectName objectName = inURN.toObjectName(); return registerMXBean(inMXBean, objectName); } /** * Registers the supplied Object instance with the MBean server. * * @param inMXBean the module factory / instance. * @param inObjectName The object name to use when registering the MBean * * @return the objectName of the registered bean * * @throws BeanRegistrationException if there were errors * registering the bean. */ private ObjectName registerMXBean(Object inMXBean, ObjectName inObjectName) throws BeanRegistrationException { Set<Class<?>> intfs = new HashSet<Class<?>>(); for (Class<?> clazz = inMXBean.getClass(); !Object.class.equals(clazz); clazz = clazz.getSuperclass()) { //We should need to only implement MXBean interfaces //However there are other interfaces that are special for //JMX, like NotificationEmitter, DynamicMBean, etc. //And there might be more of such interfaces in the future //Have the proxy implement all the interfaces, it's harmless //for the interfaces that are not used by the MBeanServer. intfs.addAll(Arrays.asList(clazz.getInterfaces())); } Object proxy = inMXBean; if (!intfs.isEmpty()) { proxy = Proxy.newProxyInstance(inMXBean.getClass().getClassLoader(), intfs.toArray(new Class<?>[intfs.size()]), new SetContextClassLoaderWrapper(inMXBean)); } try { //Register the factory with the mbean server getMBeanServer().registerMBean(proxy, inObjectName); } catch (JMException e) { throw new BeanRegistrationException(e, new I18NBoundMessage1P( Messages.BEAN_REGISTRATION_ERROR, inObjectName)); } return inObjectName; } /** * The invocation handler for each Module / Factory MX Bean. This * invocation handler sets up the thread context classloader to be * the same value as the classloader for the class Module / Factory * delegated to. */ private class SetContextClassLoaderWrapper implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Thread thread = Thread.currentThread(); ClassLoader loader = thread.getContextClassLoader(); try { thread.setContextClassLoader(ModuleManager.this.mClassLoader); return method.invoke(mDelegate, args); } catch(InvocationTargetException e) { throw e.getCause(); } finally { thread.setContextClassLoader(loader); } } /** * Creates an instance. * * @param inDelegate the MBean instance that needs to wrapped up. * Cannot be null. */ private SetContextClassLoaderWrapper(Object inDelegate) { if(inDelegate == null) { throw new NullPointerException(); } mDelegate = inDelegate; } private final Object mDelegate; } /** * Initialize the writable attributes of the supplied module * instance / factory mbean with default values, if available. * * Only those attributes which can be converted from string values * by {@link StringToTypeConverter} are set via this * mechanism. * * @param inURN the module factory / instance URN * @param inObjectName the corresponding object name for the module * factory / instance. * * @throws MXBeanOperationException If there were errors carrying out * MXBean attribute discovery or set operations. */ private void initializeDefaultValues(ModuleURN inURN, ObjectName inObjectName) throws ModuleException { if(mConfigurationProvider == null) { //no default values available, skip. return; } //Iterate through all the attributes try { for(MBeanAttributeInfo attrib: getMBeanServer().getMBeanInfo(inObjectName).getAttributes()) { //If the attribute is writable and of type whose default //value setting is supported if(attrib.isWritable() && MBeanAttributeSetterHelper.isSupported(attrib)) { //Get the default value for the attribute String defValue = mConfigurationProvider.getDefaultFor( inURN, attrib.getName()); //If the default value is available if(defValue != null && !defValue.trim().isEmpty()) { //Set the default value on the bean MBeanAttributeSetterHelper.setValue(getMBeanServer(), inObjectName, attrib, defValue); SLF4JLoggerProxy.debug(this, "Set {} bean's attribute {} to '{}'", //$NON-NLS-1$ inObjectName, attrib.getName(),defValue); } } } } catch (ModuleException e) { throw e; } catch (RuntimeException e) { //bean setters may throw runtime exception throw new MXBeanOperationException(e, new I18NBoundMessage1P( Messages.BEAN_ATTRIB_DISCOVERY_ERROR, inObjectName)); } catch (JMException e) { throw new MXBeanOperationException(e, new I18NBoundMessage1P( Messages.BEAN_ATTRIB_DISCOVERY_ERROR, inObjectName)); } } /** * Creates a new module instance. * * @param inFactory the module factory instance * @param inParameters the parameters supplied to create new * module instance. * * @return the new module instance * * @throws ModuleCreationException if an attempt was made to create * multiple instances of a singleton module OR if a module with the same * URN already exists. * @throws InvalidURNException if the created module's URN failed URN * validation. * @throws MXBeanOperationException if there problems registering * the module's MXBean with the MBean server. * @throws ModuleException if there was an error creating a new * module instance OR if this was an auto-start module, if there were * errors starting it. */ private Module createModule(ModuleFactory inFactory, Object... inParameters) throws ModuleException { Module module; Lock factoryLock = null; ModuleURN urn; try { //Singleton factory create operations are synchronized on the factory //to ensure that a singleton factory is not asked to create more //than one instance. //Multiple factory create operations are synchronized on the factory //to ensure that it doesn't create modules with duplicate URNs. factoryLock = inFactory.getLock(); factoryLock.lock(); if(inFactory.isMultipleInstances()) { module = inFactory.create(inParameters); } else { List<ModuleURN> urns = getModuleInstances(inFactory.getProviderURN()); if(!urns.isEmpty()) { throw new ModuleCreationException(new I18NBoundMessage2P( Messages.CANNOT_CREATE_SINGLETON, inFactory.getProviderURN().toString(), urns.get(0).toString())); } module = inFactory.create(inParameters); } urn = module.getURN(); //validate module's URN, verify that it's of provider's type URNUtils.validateInstanceURN(urn, inFactory.getProviderURN()); //Verify that a duplicate module is not being created. //We already have a factory lock and only the same factory //has the capability to create a module having a duplicate URN. //This scheme prevents concurrent instantiation of module instances //from the same provider, but is easier to implement than //implementing unique, module URN based locks. if(mModules.has(urn)) { throw new ModuleCreationException(new I18NBoundMessage1P( Messages.DUPLICATE_MODULE_URN,urn.toString())); } //if its an mbean, register it and initialize its default values if(isMXBean(module)) { ObjectName objectName = registerMXBean(urn, module); Messages.LOG_REGISTERED_MODULE_BEAN.info(this, urn, objectName); //Configure default values if available boolean initDefaultValuesFailed = true; try { initializeDefaultValues(urn,objectName); initDefaultValuesFailed = false; } finally { if(initDefaultValuesFailed) { //unregister the MBean. try { unregister(objectName); } catch (JMException e) { SLF4JLoggerProxy.debug(this, e, "Error unregistering MBean {} on init default value failure", objectName); //$NON-NLS-1$ } } } } //add the module to appropriate lookup tables mModules.add(module); } finally { if(factoryLock != null) { factoryLock.unlock(); } } Messages.LOG_CREATED_MODULE_INSTANCE.info(this, urn); //if its auto-start, try starting it if(module.isAutoStart()) { startModule(module); } return module; } /** * Starts the module. * * @param inModule the module instance that needs to be started. * * @throws ModuleStateException if the module is not in the correct * state to be started. * @throws ModuleException if {@link Module#preStart()} threw an exception * OR if there were other errors starting the module. */ private void startModule(Module inModule) throws ModuleException { Lock moduleLock = inModule.getLock().writeLock(); //Acquire the lock for module state changes moduleLock.lock(); try { //Verify module still exists, in case it got deleted after we //found it but before we acquired the lock getModule(inModule.getURN()); //Verify module state ModuleState state = inModule.getState(); if(!state.canBeStarted()) { throw new ModuleStateException(new I18NBoundMessage3P( Messages.MODULE_NOT_STARTED_STATE_INCORRECT, inModule.getURN().toString(), state, ModuleState.STARTABLE_STATES.toString())); } //Tell the module that its about to be started inModule.setState(ModuleState.STARTING); } finally { moduleLock.unlock(); } boolean startSucceeded = false; try { if(inModule instanceof DataFlowRequester) { //supply it a data flow requester support //instance ((DataFlowRequester)inModule).setFlowSupport( new DataFlowSupportImpl(inModule,this)); } inModule.preStart(); startSucceeded = true; Messages.LOG_MODULE_STARTED.info(this, inModule.getURN()); } catch(ModuleException e) { inModule.setLastStartFailure(e.getLocalizedDetail()); Messages.LOG_START_MODULE_FAILED.warn(this,e,inModule.getURN()); throw e; } finally { //Acquire the lock for module state changes. moduleLock.lock(); try { if(startSucceeded) { inModule.setState(ModuleState.STARTED); inModule.setLastStartFailure(null); } else { inModule.setState(ModuleState.START_FAILED); } } finally { moduleLock.unlock(); } if (!startSucceeded) { //check if the module created any data flows and if it //did, make sure they are canceled. final Set<DataFlowID> flows = mDataFlows.getInitiatedFlows( inModule.getURN()); if(flows != null && !flows.isEmpty()) { for(DataFlowID id: flows) { Messages.LOG_CANCEL_DATA_FLOW_START_FAILED.info( this, id, inModule.getURN()); cancel(id); } } } } } /** * Returns true if the supplied object implements an * interface that is an MXBean interface or a DynamicMBean interface. * * @param inObject the object instance that needs to be tested * * @return if the object is an MXBean. */ private static boolean isMXBean(Object inObject) { for (Class<?> c = inObject.getClass(); !Object.class.equals(c); c = c.getSuperclass()) { for(Class<?> intf: c.getInterfaces()) { if(JMX.isMXBeanInterface(intf) || DynamicMBean.class.equals(intf)) { return true; } } } return false; } /** * The JMX domain name for all the module framework beans */ public static final String MBEAN_DOMAIN_NAME = ModuleManager.class.getPackage().getName(); /** * The name for the module manager MXBean */ public static final String MODULE_MBEAN_NAME = MBEAN_DOMAIN_NAME + ":name=" + //$NON-NLS-1$ ModuleManager.class.getSimpleName(); /** * The default maximum flow history to maintain. */ public static final int DEFAULT_MAX_FLOW_HISTORY = 10; /** * Array of listeners that listen to data received by the data sink */ private volatile SinkDataListener[] mSinkListeners = new SinkDataListener[0]; /** * The listener for {@link #refresh()} invocations. */ private RefreshListener mRefreshListener; /** * The lock object used to synchronize concurrent updates to * <code>mSinkListeners</code> */ private final Object mSinkListenerLock = new Object(); /** * The lock object used to synchronize all the module and data * flow operations. */ private final Object mOperationsLock = new Object(); /** * Table of module instances. */ private final ModuleInstanceTracker mModules = new ModuleInstanceTracker(); /** * Table of module factory instances. */ private final Map<ModuleURN,ModuleFactory> mModuleFactories = new HashMap<ModuleURN, ModuleFactory>(); /** * The service loader used to discover and instantiate module * providers */ private final ServiceLoader<ModuleFactory> mLoader; /** * The classloader to use for loading providers. */ private final ClassLoader mClassLoader; /** * The module configuration provider that provides default configuration * properties for module factories and instances. */ private volatile ModuleConfigurationProvider mConfigurationProvider; /** * The table of currently active data flows. */ private final DataFlowTracker mDataFlows = new DataFlowTracker(); /** * History of flows that are not active any more */ private final Deque<DataFlowInfo> mFlowHistory = new LinkedList<DataFlowInfo>(); /** * Maximum number flow histories to keep a record of. */ private volatile int mMaxFlowHistory = DEFAULT_MAX_FLOW_HISTORY; /** * The MBean server to use for all JMX operations. */ private MBeanServer mMBeanServer = ManagementFactory.getPlatformMBeanServer(); /** * singleton ModuleManager instance */ @GuardedBy("ModuleManager.class") private static ModuleManager instance; }