package org.oddjob; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import javax.inject.Inject; import org.oddjob.arooa.ArooaAnnotations; import org.oddjob.arooa.ArooaBeanDescriptor; import org.oddjob.arooa.ArooaConfiguration; import org.oddjob.arooa.ArooaDescriptor; import org.oddjob.arooa.ArooaParseException; import org.oddjob.arooa.ArooaSession; import org.oddjob.arooa.ArooaValue; import org.oddjob.arooa.ConfigurationHandle; import org.oddjob.arooa.ConfiguredHow; import org.oddjob.arooa.ParsingInterceptor; import org.oddjob.arooa.convert.ArooaConversionException; import org.oddjob.arooa.convert.ArooaConverter; import org.oddjob.arooa.deploy.ArooaDescriptorBean; import org.oddjob.arooa.deploy.ArooaDescriptorFactory; import org.oddjob.arooa.deploy.ListDescriptorBean; import org.oddjob.arooa.deploy.NoAnnotations; import org.oddjob.arooa.deploy.annotations.ArooaAttribute; import org.oddjob.arooa.life.ComponentPersistException; import org.oddjob.arooa.life.ComponentPersister; import org.oddjob.arooa.parsing.ArooaContext; import org.oddjob.arooa.parsing.ArooaElement; import org.oddjob.arooa.parsing.ConfigConfigurationSession; import org.oddjob.arooa.parsing.ConfigurationOwner; import org.oddjob.arooa.parsing.ConfigurationOwnerSupport; import org.oddjob.arooa.parsing.ConfigurationSession; import org.oddjob.arooa.parsing.DragPoint; import org.oddjob.arooa.parsing.HandleConfigurationSession; import org.oddjob.arooa.parsing.OwnerStateListener; import org.oddjob.arooa.parsing.SerializableDesignFactory; import org.oddjob.arooa.parsing.SessionStateListener; import org.oddjob.arooa.registry.BeanDirectory; import org.oddjob.arooa.registry.BeanDirectoryOwner; import org.oddjob.arooa.registry.ServiceProvider; import org.oddjob.arooa.registry.Services; import org.oddjob.arooa.standard.StandardArooaParser; import org.oddjob.arooa.types.ArooaObject; import org.oddjob.arooa.types.ValueType; import org.oddjob.arooa.types.XMLConfigurationType; import org.oddjob.arooa.utils.RootConfigurationFileCreator; import org.oddjob.arooa.xml.XMLConfiguration; import org.oddjob.designer.components.RootDC; import org.oddjob.framework.ComponentBoundry; import org.oddjob.framework.StructuralJob; import org.oddjob.input.InputHandler; import org.oddjob.jobs.EchoJob; import org.oddjob.oddballs.OddballsDescriptorFactory; import org.oddjob.persist.FilePersister; import org.oddjob.persist.OddjobPersister; import org.oddjob.scheduling.DefaultExecutors; import org.oddjob.scheduling.OddjobServicesBean; import org.oddjob.sql.SQLPersisterService; import org.oddjob.state.IsHardResetable; import org.oddjob.state.IsNot; import org.oddjob.state.IsSoftResetable; import org.oddjob.state.ParentState; import org.oddjob.state.StateConditions; import org.oddjob.state.StateEvent; import org.oddjob.state.StateListener; import org.oddjob.state.StateOperator; import org.oddjob.state.WorstStateOp; import org.oddjob.util.OddjobConfigException; import org.oddjob.util.URLClassLoaderType; import org.oddjob.values.properties.PropertiesType; /** * Read a configuration, creates child jobs and executes them. * * @oddjob.description The starting point for a hierarchy of jobs. The Oddjob job * creates and runs a job hierarchy by processing a supplied configuration. * <p> * Oddjob creates a 'root' job on which to create the hierarchy. Through this * root Oddjob aquires the first job to run and also exposes some of it's own * properties for the jobs in the configuration to use. The root job's properties * are: * * <dl> * <dt><b>job</b></dt> * <dd>The top level job. This is the single job that Oddjob runs. This property is * optional but Oddjob won't do much if a job for it to run isn't supplied. * This is the Oddjob root's only writeable property and is write only.</dd> * <dt><b>file</b></dt> * <dd>The path of the configuration file that Oddjob has loaded. Read Only.</dd> * <dt><b>dir</b></dt> * <dd>The path of the configuration file's directory. Read Only.</dd> * <dt><b>args</b></dt> * <dd>An array of arguments passed in on the command line or from a parent * Oddjob. See below. Read Only.</dd> * <dt><b>services</b></dt> * <dd>Provides access to Oddjobs underlying services. Used by * the frameworks automatic configuration mechanism to configure the properties * of jobs that are documented as set automatically. May be ignored for every day * use. Read Only.</dd> * </dl> * * <p>For these properties to be accessible the root oddjob must be given an id. * As can be seen from the examples, the author uses the id '<code>this</code>' * but the choice is arbitrary.</p> * * <h4>Nesting Oddjobs</h4> * * <p> * An Oddjob job allows an Oddjob instance to be created within an existing Oddjob * configuration. This way complicated processes can be created in manageable and * separately testable units. * <p> * Properties of jobs in a nested Oddjob can be accessed using the notation * <i>${nested-oddjob-id/job-id.property}</i> where nested-oddjob-id is the id in * the outer configuration, not the inner one. * * <h4>Saving Oddjob's State</h4> * * <p> * The <code>persister</code> property on a nested Oddjob will allow it's state to * be saved. See the * <a href="http://rgordon.co.uk/projects/oddjob/userguide/saving.html">User Guide</a> * for more information on how to set a persister. * * <h4>Customising Oddjob</h4> * * <p> * Oddjob's <code>descriptorFactory</code> and <code>classLoader</code> properties * allow bespoke components and * types to be used. The * <a href="http://rgordon.co.uk/projects/oddjob/devguide/index.html">developer guide</a> * is all about writing custom job's for Oddjob. * <p> * * * @oddjob.example * * Hello World with Oddjob. Oddjob is configured to run the {@link EchoJob} job. * * {@oddjob.xml.resource org/oddjob/HelloWorld.xml} * * @oddjob.example * * Using an argument passed into Oddjob that may or may not be set. * * {@oddjob.xml.resource org/oddjob/OptionalFileNameArg.xml} * * @oddjob.example * * Nesting Oddjob. Note how the <code>dir</code> property of the * Oddjob root is used as the path of the nested configuration file. * * {@oddjob.xml.resource org/oddjob/NestedOddjob.xml} * * <p> * The nested job is the first example: * * {@oddjob.xml.resource org/oddjob/HelloWorld.xml} * * This example also shows how a property within the nested file can be * accessed within the parent configuration. * * @oddjob.example * * A nested Oddjob with one argument passed to the child. * * {@oddjob.xml.resource org/oddjob/NestedOddjobWithArg.xml} * * And EchoArg.xml: * * {@oddjob.xml.resource org/oddjob/EchoArg.xml} * * @oddjob.example * * A nested Oddjob with a property past to the child. * * {@oddjob.xml.resource org/oddjob/NestedOddjobWithProperty.xml} * * And EchoProperty.xml: * * {@oddjob.xml.resource org/oddjob/EchoProperty.xml} * * Unlike the properties of jobs, free format properties like this can't be * accessed using the nested convention. * <pre> * ${nested/our.greeting} DOES NOT WORK! * </pre> * This may be fixed in future versions. * * @oddjob.example * * Using export to pass values to a nested Oddjob. * * {@oddjob.xml.resource org/oddjob/OddjobExportJobTest.xml} * * Here a job is exported into a nested Oddjob. The * exported object is actually a {@link ValueType}. The value is converted back * to the job when the job property of the run job is set. Expressions such * as <code>${secret.text}</code> are not valid (because value does not have a * text property!). Even <code>${secret.value.text}</code> will not work because * of value wraps the job in yet another layer of complexity. * * @oddjob.example * * Examples elsewhere. * <ul> * <li>{@link FilePersister}</li> * <li>{@link SQLPersisterService}</li> * <li>{@link OddballsDescriptorFactory}</li> * <li>{@link URLClassLoaderType}</li> * </ul> * <p> * * @author Rob Gordon * */ public class Oddjob extends StructuralJob<Object> implements Loadable, ConfigurationOwner, BeanDirectoryOwner { private static final long serialVersionUID = 2010051200L; /** The document root for an Oddjob configuration file. */ public static final ArooaElement ODDJOB_ELEMENT = new ArooaElement("oddjob"); /** For console capture. */ private volatile transient OddjobConsole.Close console; /** The configuration file. */ private volatile File file; /** This will be true when there is a restored configuration. * Setting a new configuration will be ignored while this flag * is true. A reset operation will clear this flag. */ private transient volatile boolean restored; /** * @oddjob.property * @oddjob.description The configuration. An alternative to * setting a file. This can be useful when the configuration * is to come from some other input. * <p> * See also {@link XMLConfigurationType} * * @oddjb.required Either this or file is required. */ private transient volatile ArooaConfiguration configuration; /** Support for configuration modification. */ private transient volatile ConfigurationOwnerSupport configurationOwnerSupport; /** * @oddjob.property * @oddjob.description A component which is able to save and restore * jobs. * <p> * See also {@link FilePersister} and {@link SQLPersisterService}. * * @oddjob.required No. */ private transient volatile OddjobPersister persister; /** * @oddjob.property * @oddjob.description An array of arguments the Oddjob configuration can use. * @oddjob.required No. */ private volatile String[] args; /** Class loader */ private transient volatile ClassLoader classLoader; /** * @oddjob.property * @oddjob.description An {@link ArooaDescriptorFactory} that * will be used when loading the configuration. This augments Oddjob's * internal descriptor, and allows custom jobs to have their own * definitions. * <p> * See also {@link ArooaDescriptorBean}, {@link ListDescriptorBean} * and {@link OddballsDescriptorFactory} * * @oddjob.required No. */ private transient volatile ArooaDescriptorFactory descriptorFactory; /** The session that is created on loading. */ private transient ArooaSession ourSession; /** The root. */ private transient Object oddjobRoot; /** Executors used if none are provided. */ private transient volatile DefaultExecutors internalExecutors; /** * @oddjob.property * @oddjob.description Executors for Oddjob to use. This is * set automatically in Oddjob. For advanced use, user * supplied {@link OddjobExecutors} may be provided. * @oddjob.required No. */ private transient volatile OddjobExecutors oddjobExecutors; /** * @oddjob.property * @oddjob.description Services for Oddjob to use. This is * set automatically in Oddjob. Unlikely to be required. * @oddjob.required No. */ private transient volatile OddjobServices oddjobServices; /** * @oddjob.property * @oddjob.description Values to be exported into the nested * configuration. Values will be registered in the inner * oddjob using the key of this mapped property. * @oddjob.required No */ private transient volatile Map<String, ArooaValue> export; /** * @oddjob.property * @oddjob.description Properties to be set in the nested * configuration. Can be set using a {@link PropertiesType}. * @oddjob.required No */ private volatile Properties properties; /** * @oddjob.property * @oddjob.description Set how an Oddjob should share the values and * properties of it's parent. Valid values are: * <dl> * <dt>NONE</dt> * <dd>No values or properties are automatically inherited.</dd> * <dt>PROPERTIES</dt> * <dd>All properties are inherited. Only properties are inherited, values * must be exported explicitly using the export property.</dd> * <dt>SHARED</dt> * <dd>All properties and values are shared between the parent and child * Oddjobs. Any properties or values set in the child will be visible * in the parent. This setting is particularly useful for shared common * configuration.</dd> * @oddjob.required No. Defaults to PROPERTIES. */ private volatile OddjobInheritance inheritance; /** * Values for resets. */ enum Reset { SOFT, HARD; } /** * @oddjob.property * @oddjob.description Used internally to remember which * reset to apply after loading a configuration. */ private Reset lastReset; /** * @oddjob.property * @oddjob.description A handler for user input. This will be * provided internally and will only be required in specialised * situations. * @oddjob.required No. */ private transient InputHandler inputHandler; /** * Only constructor. */ public Oddjob() { completeConstruction(); } private void completeConstruction() { console = OddjobConsole.initialise(); configurationOwnerSupport = new ConfigurationOwnerSupport(this); } /** * @oddjob.property file * @oddjob.description The name of the configuration file. * to configure this oddjob. * @oddjob.required No. * * @return The file name. */ @ArooaAttribute public void setFile(File file) { if (restored) { return; } this.file = file; if (file == null) { // during destroy clean up. Does this matter? configuration = null; } } public File getFile() { if (file == null) { return null; } return file.getAbsoluteFile(); } /** * Setter for configuration. * * @param config */ public void setConfiguration(ArooaConfiguration config) { if (restored) { return; } this.configuration = config; } /** * @oddjob.property classLoader * @oddjob.description The classLoader to use when loading * the configuration. * <p> * See also {@link URLClassLoaderType} * * @oddjob.required No. */ public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } /** * Return a class loader. If one has not been set return * the class loader which loaded this class. * * @return A classLoader. */ public ClassLoader getClassLoader() { return this.classLoader; } /* * (non-Javadoc) * @see org.oddjob.arooa.parsing.ConfigurationOwner#provideConfigurationSession() */ @Override public ConfigurationSession provideConfigurationSession() { return configurationOwnerSupport.provideConfigurationSession(); } /* * (non-Javadoc) * @see org.oddjob.arooa.parsing.ConfigurationOwner#addOwnerStateListener(org.oddjob.arooa.parsing.OwnerStateListener) */ @Override public void addOwnerStateListener(OwnerStateListener listener) { configurationOwnerSupport.addOwnerStateListener(listener); } /* * (non-Javadoc) * @see org.oddjob.arooa.parsing.ConfigurationOwner#removeOwnerStateListener(org.oddjob.arooa.parsing.OwnerStateListener) */ @Override public void removeOwnerStateListener(OwnerStateListener listener) { configurationOwnerSupport.removeOwnerStateListener(listener); } @Override public SerializableDesignFactory rootDesignFactory() { return new RootDC(); } @Override public ArooaElement rootElement() { return ODDJOB_ELEMENT; } /* * (non-Javadoc) * @see org.oddjob.framework.StructuralJob#getStateOp() */ @Override protected StateOperator getInitialStateOp() { return new WorstStateOp(); } /** * Set our internal session. Can't remember why this is a method. * @param session */ private void setOurSession(ArooaSession session) { this.ourSession = session; } /** * Shared initialisation between load and execute. * * @return */ private OddjobServices preLoadInitialisation() { // When configured by a file, check it exists and use // it as Oddjob's configuration. if (file != null) { new RootConfigurationFileCreator( ODDJOB_ELEMENT).createIfNone(file); configuration = new XMLConfiguration(file); } if (configuration == null){ throw new IllegalStateException("No configuration given."); } ClassLoader classLoader = this.classLoader; if (classLoader == null) { if (oddjobServices == null) { classLoader = getClass().getClassLoader(); } else { classLoader = oddjobServices.getClassLoader(); } } OddjobExecutors oddjobExecutors = this.oddjobExecutors; if (oddjobExecutors == null) { if (oddjobServices == null) { internalExecutors = new DefaultExecutors(); oddjobExecutors = internalExecutors; } else { oddjobExecutors = oddjobServices.getOddjobExecutors(); } } InputHandler inputHandler = this.inputHandler; if (inputHandler == null && oddjobServices != null) { inputHandler = oddjobServices.getInputHandler(); } OddjobServicesBean services = new OddjobServicesBean(); services.setClassLoader(classLoader); services.setOddjobExecutors(oddjobExecutors); services.setInputHandler(inputHandler); OddjobSessionFactory sessionFactory = new OddjobSessionFactory(); sessionFactory.setExistingSession(getArooaSession()); sessionFactory.setClassLoader(classLoader); sessionFactory.setDescriptorFactory(descriptorFactory); sessionFactory.setOddjobPersister(persister); sessionFactory.setProperties(properties); sessionFactory.setInherit(inheritance); ArooaSession newSession = sessionFactory.createSession(this); if (export != null) { ArooaConverter converter = newSession.getTools().getArooaConverter(); for (Map.Entry<String, ArooaValue> entry: export.entrySet()) { String name = entry.getKey(); ArooaValue value = entry.getValue(); try { ArooaObject object = converter.convert(value, ArooaObject.class); if (object == null) { logger().info("Bean to export with id " + name + " is null."); } else { newSession.getBeanRegistry().register( name, object.getValue()); } } catch (ArooaConversionException e) { newSession.getBeanRegistry().register( name, value); } } } // Bean directory needs to be available during creation. setOurSession(newSession); return services; } /** * Load Oddjob from the configuration file. */ private void doLoad(OddjobServices oddjobServices) throws Exception { logger().info("Loading from configuration " + configuration); oddjobRoot = new OddjobRoot(oddjobServices); StandardArooaParser parser = new StandardArooaParser( oddjobRoot, ourSession); parser.setExpectedDocumentElement( ODDJOB_ELEMENT); try { ConfigurationHandle configHandle = parser.parse(configuration); configurationOwnerSupport.setConfigurationSession( new OddjobConfigurationSession( new HandleConfigurationSession( ourSession, configHandle))); } catch (Exception e) { // This will edit a failed configuration in case of // failure. configurationOwnerSupport.setConfigurationSession( new ConfigConfigurationSession( ourSession, configuration)); // Reset on failure. setOurSession(null); throw e; } logger().debug("Loaded."); } /** * @oddjob.property loadable * @oddjob.description Can Oddjob be loaded. Used by the Load/Unload actions. * @oddjob.required Read only. */ @Override public boolean isLoadable() { return ourSession == null; } /* * (non-Javadoc) * @see org.oddjob.Loadable#load() */ @Override public void load() { ComponentBoundry.push(loggerName(), this); try { stateHandler().waitToWhen(new IsNot(StateConditions.RUNNING), new Runnable() { public void run() { try { if (ourSession != null) { return; } configure(); OddjobServices services = preLoadInitialisation(); doLoad(services); } catch (Exception e) { logger().error("Exception executing job.", e); getStateChanger().setStateException(e); } } }); } finally { ComponentBoundry.pop(); } } /* * (non-Javadoc) * @see org.oddjob.jobs.AbstractJob#execute() */ @Override protected void execute() throws Exception { if (ourSession == null) { OddjobServices services = preLoadInitialisation(); doLoad(services); if (lastReset != null) { switch (lastReset) { case SOFT: logger().debug("Re-doing Soft Reset on children after load."); childHelper.softResetChildren(); break; case HARD: logger().debug("Re-doing Hard Reset on children after load."); childHelper.hardResetChildren(); break; } lastReset = null; } } Object child = childHelper.getChild(); // if there is something to execute, execute it. if (child != null && child instanceof Runnable && !stop) { ((Runnable) child).run(); } } /* * (non-Javadoc) * @see org.oddjob.Loadable#unload() */ @Override public void unload() { reset(); } /** * Actions to be performed when resetting Oddjob. * */ private void reset() { if (ourSession == null) { // being destroyed but hasn't been loaded. return; } configurationOwnerSupport.setConfigurationSession(null); ComponentPersister persister = ourSession.getComponentPersister(); if (persister != null) { persister.close(); } ArooaContext oddjobContext = ourSession.getComponentPool().contextFor(oddjobRoot); // Context could be null if the document element was rubbish. if (oddjobContext != null) { oddjobContext.getRuntime().destroy(); } oddjobRoot = null; setOurSession(null); } /** * Part of Oddjob's internals. Stop the Executors, if we started them. */ void stopExecutors() { // Try and be thread safe about the null pointer possibility. DefaultExecutors executors = internalExecutors; if (executors != null) { executors.stop(); internalExecutors = null; } } @Override protected void onDestroy() { super.onDestroy(); reset(); stopExecutors(); console.close(); } @Override public boolean softReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler().waitToWhen(new IsSoftResetable(), new Runnable() { public void run() { logger().debug("Soft reset requested."); if (ourSession == null) { if (lastReset == null) { lastReset = Reset.SOFT; if (!saveLastReset()) { return; } } } else { lastReset = null; } superSoftReset(); restored = false; } }); } finally { ComponentBoundry.pop(); } } /** * Required because Java doesn't support Oddjob.this.super.softReset from * within inner classes. */ private void superSoftReset() { super.softReset(); } /** * Save the last reset level. Required because there may be a break * between a reset and applying the reset to children. * * @return True if saved. */ private boolean saveLastReset() { if (stateHandler().getState() != ParentState.READY) { return true; } try { save(); } catch (ComponentPersistException e) { getStateChanger().setStateException(e); return false; } return true; } /** * Perform a hard reset. The super method is overridden * so as not to reset the child but destroy them. */ public boolean hardReset() { ComponentBoundry.push(loggerName(), this); try { return stateHandler().waitToWhen(new IsHardResetable(), new Runnable() { public void run() { logger().debug("Hard Reset requested."); if (ourSession == null) { lastReset = Reset.HARD; if (!saveLastReset()) { return; } } else { lastReset = null; } childStateReflector.stop(); childHelper.hardResetChildren(); reset(); stop = false; restored = false; getStateChanger().setState(ParentState.READY); logger().info("Hard Reset complete."); } }); } finally { ComponentBoundry.pop(); } } /** * @return Returns the persister. */ public OddjobPersister getPersister() { return persister; } /** * @param persister The persister to set. */ public void setPersister(OddjobPersister persister) { this.persister = persister; } /* * (non-Javadoc) * @see org.oddjob.arooa.registry.BeanDirectoryOwner#provideBeanDirectory() */ @Override public BeanDirectory provideBeanDirectory() { if (ourSession == null) { return null; } return ourSession.getBeanRegistry(); } /** * Getter for last reset. * * @return */ public Reset getLastReset() { return lastReset; } /* * Custom serialization. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); String config = null; ConfigurationSession session = provideConfigurationSession(); if (session != null) { DragPoint root = session.dragPointFor(this); if (root != null) { config = root.copy(); } } s.writeObject(config); } /* * Custom serialization. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); String config = (String) s.readObject(); if (config != null) { this.configuration = new XMLConfiguration( "Restored Configuration", config); this.restored = true; } completeConstruction(); } /** * @return Returns the args. */ public String[] getArgs() { return args; } /** * @param args The args to set. */ public void setArgs(String[] args) { this.args = args; } /** * Getter * * @param key The key. * * @return A value. */ public ArooaValue getExport(String key) { return export.get(key); } /** * Setter. * * @param key * @param value */ public void setExport(String key, ArooaValue value) { if (export == null) { export = new LinkedHashMap<String, ArooaValue>(); } if (value == null && export.containsKey(key)) { export.remove(key); } else { ComponentBoundry.push(loggerName(), this); try { logger().debug("Adding value to export: " + key + "=" + value); export.put(key, value); } finally { ComponentBoundry.pop(); } } } /** * Getter. * * @return Properties, if set, or null. */ public Properties getProperties() { return properties; } /** * Setter. * * @param properties Optional properties. */ public void setProperties(Properties properties) { this.properties = properties; } /** * Getter. * * @return Inheritance property. */ public OddjobInheritance getInheritance() { return inheritance; } /** * Setter. * * @param inheritance Inheritance property. */ public void setInheritance(OddjobInheritance inheritance) { this.inheritance = inheritance; } /** * @oddjob.property dir * @oddjob.description The name of the directory the configuration * file is in. * @oddjob.required R/O * * @return The directory path. */ public File getDir() { if (file == null) { return null; } return file.getAbsoluteFile().getParentFile(); } /** * @oddjob.property version * @oddjob.description This Oddjob's version. * * @return The version. */ public String getVersion() { return Version.getCurrentVersionAndBuildDate(); } /** * The object which is the Oddjob root. * */ public class OddjobRoot implements Stateful, ServiceProvider, ConfigurationOwner { private final OddjobServices oddjobServices; OddjobRoot(OddjobServices services) { this.oddjobServices = services; } @Override public void addOwnerStateListener(OwnerStateListener listener) { Oddjob.this.addOwnerStateListener(listener); } @Override public void removeOwnerStateListener(OwnerStateListener listener) { Oddjob.this.removeOwnerStateListener(listener); } @Override public SerializableDesignFactory rootDesignFactory() { return Oddjob.this.rootDesignFactory(); } @Override public ArooaElement rootElement() { return Oddjob.this.rootElement(); } @Override public ConfigurationSession provideConfigurationSession() { return Oddjob.this.provideConfigurationSession(); } public void setJob(Object child) { if (child == null) { logger().debug("Removing child."); childHelper.removeChildAt(0); } else { logger().debug("Adding child [" + child + "]"); if (Oddjob.this.childHelper.getChild() != null) { throw new OddjobConfigException( "Oddjob can't have more than one child component."); } childHelper.insertChild(0, child); } } public void addStateListener(StateListener listener) { Oddjob.this.addStateListener(listener); } public void removeStateListener(StateListener listener) { Oddjob.this.removeStateListener(listener); } @Override public StateEvent lastStateEvent() { return Oddjob.this.lastStateEvent(); } public File getFile() { if (file == null) { return null; } return file.getAbsoluteFile(); } public File getDir() { if (file == null) { return null; } return file.getAbsoluteFile().getParentFile(); } /** * @return Returns the args. */ public Object[] getArgs() { if (args == null) { // Oddjob Main will always set an empty String array when // no args are passed in. We want the same behaviour when // Oddjob is used embedded. return new String[0]; } else { return args; } } public Services getServices() { return oddjobServices; } public ClassLoader getClassLoader() { return oddjobServices.getClassLoader(); } } /** * Provide an {@link ArooaBeanDescriptor} for the root Oddjob bean. * */ public static class OddjobRootArooa implements ArooaBeanDescriptor { public ParsingInterceptor getParsingInterceptor() { return null; } public String getTextProperty() { return null; } public String getComponentProperty() { return "job"; } public ConfiguredHow getConfiguredHow(String property) { return ConfiguredHow.ELEMENT; } public String getFlavour(String property) { return null; } public boolean isAuto(String property) { return false; } @Override public ArooaAnnotations getAnnotations() { return new NoAnnotations(); } } /** * Getter. * * @return An ArooaDescriptorFactory or null. */ public ArooaDescriptorFactory getDescriptorFactory() { return descriptorFactory; } /** * Setter. * * @param descriptorFactory And ArooaDescriptorFactory. */ public void setDescriptorFactory(ArooaDescriptorFactory descriptorFactory) { this.descriptorFactory = descriptorFactory; } /** * Getter for {@link OddjobExecutors} that have been given to this * instance of Oddjob. This getter does not expose the internal executors. * * @return OddjobExecutors or null. */ public OddjobExecutors getOddjobExecutors() { return oddjobExecutors; } /** * Setter. * * @param executors OddjobExecutors */ public void setOddjobExecutors(OddjobExecutors executors) { this.oddjobExecutors = executors; } /** * Getter for {@link OddjobServices}. * * @return */ public OddjobServices getOddjobServices() { return oddjobServices; } /** * Allow for injection of {@link OddjobServices} * * @param oddjobServices */ @Inject public void setOddjobServices(OddjobServices oddjobServices) { this.oddjobServices = oddjobServices; } @Override public String toString() { String name = getName(); if (name != null) { return name; } if (file != null) { return "Oddjob " + file.getName(); } return getClass().getSimpleName(); } /** * Wrapper to replace this Oddjob with the root object. */ class OddjobConfigurationSession implements ConfigurationSession { private final ConfigurationSession delegate; public OddjobConfigurationSession(ConfigurationSession delegate) { this.delegate = delegate; } public DragPoint dragPointFor(Object component) { // required for the Design Inside action. if (component == Oddjob.this) { component = Oddjob.this.oddjobRoot; } return delegate.dragPointFor(component); } public ArooaDescriptor getArooaDescriptor() { return delegate.getArooaDescriptor(); } public void save() throws ArooaParseException { delegate.save(); } public boolean isModified() { return delegate.isModified(); } public void addSessionStateListener(SessionStateListener listener) { delegate.addSessionStateListener(listener); } public void removeSessionStateListener(SessionStateListener listener) { delegate.removeSessionStateListener(listener); } } /** * Getter. * * @return An InputHandler. */ public InputHandler getInputHandler() { return inputHandler; } /** * Setter. * * @param inputHandler An InputHandler. */ public void setInputHandler(InputHandler inputHandler) { this.inputHandler = inputHandler; } }