package org.oddjob.framework;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.ImageIcon;
import org.apache.log4j.Logger;
import org.oddjob.Iconic;
import org.oddjob.Stateful;
import org.oddjob.arooa.ArooaConfigurationException;
import org.oddjob.arooa.ArooaException;
import org.oddjob.arooa.ArooaSession;
import org.oddjob.arooa.deploy.annotations.ArooaHidden;
import org.oddjob.arooa.life.ArooaContextAware;
import org.oddjob.arooa.life.ArooaLifeAware;
import org.oddjob.arooa.life.ArooaSessionAware;
import org.oddjob.arooa.life.ComponentPersistException;
import org.oddjob.arooa.parsing.ArooaContext;
import org.oddjob.arooa.runtime.RuntimeEvent;
import org.oddjob.arooa.runtime.RuntimeListenerAdapter;
import org.oddjob.images.IconHelper;
import org.oddjob.images.IconListener;
import org.oddjob.state.IsAnyState;
import org.oddjob.state.StateEvent;
import org.oddjob.state.StateHandler;
import org.oddjob.state.StateListener;
/**
* An abstract implementation of a component which provides common
* functionality to jobs and services.
*
* @author Rob Gordon
*/
public abstract class BaseComponent
implements Iconic, Stateful,
ArooaSessionAware, ArooaContextAware, PropertyChangeNotifier {
/**
* Implement property change support which sub classes can take advantage of.
*/
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
/**
* A state handler to delegate state change functionality to.
* */
private volatile ArooaSession session;
/**
* Subclasses must provide a {@link StateHandler}.
*
* @return A State Handler. Never null.
*/
abstract protected StateHandler<?> stateHandler();
/**
* Subclasses must provide a {@link IconHelper}.
*
* @return An Icon Helper. Never null.
*/
abstract protected IconHelper iconHelper();
/**
* Here for the tests...
*
* @param session
*/
@Override
@ArooaHidden
public void setArooaSession(ArooaSession session) {
this.session = session;
}
/**
* Allow sub classes access the the session.
*
* @return A session. Will never be null once a component is initialised
* within Oddjob. May be null if otherwise if the session hasn't been
* set.
*/
protected ArooaSession getArooaSession() {
return this.session;
}
@Override
@ArooaHidden
public void setArooaContext(ArooaContext context) {
if (this instanceof ArooaLifeAware) {
throw new IllegalStateException(getClass().getName() +
" must not implement " + ArooaLifeAware.class.getName() +
". Methods are already available and handled internally.");
}
context.getRuntime().addRuntimeListener(new RuntimeListenerAdapter() {
@Override
public void afterInit(RuntimeEvent event) throws ArooaException {
onInitialised();
stateHandler().waitToWhen(new IsAnyState(),
new Runnable() {
@Override
public void run() {
// This is here purely for the safe publication
// of state from this creating thread to the other
// threads including the thread that will run the job.
}
});
}
@Override
public void afterConfigure(RuntimeEvent event)
throws ArooaException {
onConfigured();
}
@Override
public void beforeDestroy(RuntimeEvent event) throws ArooaException {
stateHandler().assertAlive();
ComponentBoundry.push(logger().getName(), BaseComponent.this);
try {
logger().debug("Destroying.");
onDestroy();
}
finally {
ComponentBoundry.pop();
}
}
@Override
public void afterDestroy(RuntimeEvent event) throws ArooaException {
ComponentBoundry.push(logger().getName(), BaseComponent.this);
try {
fireDestroyedState();
}
finally {
ComponentBoundry.pop();
}
}
});
}
protected abstract Logger logger();
/**
* Implementations override this to save their state on state
* change.
*
* @throws ComponentPersistException
*/
protected void save() throws ComponentPersistException {
}
/**
* Configure the runtime. If there is no runtime for this
* component then true is returned.
*
* @return true if successful. False if not.
*/
protected void configure(Object component)
throws ArooaConfigurationException {
if (session != null) {
logger().debug("Configuring.");
session.getComponentPool().configure(component);
}
}
/**
* Save this job. If there is no runtime for this
* component then true is returned.
*
* @return true if successful. False if not.
* @throws ComponentPersistException
*/
protected void save(final Object o) throws ComponentPersistException {
if (session != null) {
session.getComponentPool().save(o);
}
}
/**
* Returns the last JobState event. This is useful when Jobs are being
* used directly in code, and only one thread is using the job. Otherwise
* a JobStateListener should always be used.
* <p>
* This is not a property so that it can't be accessed directly in scripts.
*
* @return The last JobStateEvent. Will never be null.
*/
@Override
public StateEvent lastStateEvent() {
return stateHandler().lastStateEvent();
}
/**
* Add a job state listener.
*/
@Override
public void addStateListener(StateListener listener) {
stateHandler().addStateListener(listener);
}
/**
* Remove a job state listener.
*/
@Override
public void removeStateListener(StateListener listener){
stateHandler().removeStateListener(listener);
}
/**
* Add a property change listener.
*
* @param l The property change listener.
*/
@Override
public void addPropertyChangeListener(PropertyChangeListener l) {
stateHandler().assertAlive();
propertyChangeSupport.addPropertyChangeListener(l);
}
/**
* Remove a property change listener.
*
* @param l The property change listener.
*/
@Override
public void removePropertyChangeListener(PropertyChangeListener l) {
propertyChangeSupport.removePropertyChangeListener(l);
}
/**
* Fire a property change event.
*
* @param propertyName
* @param oldValue
* @param newValue
*/
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
/**
* Return an icon tip for a given id. Part
* of the Iconic interface.
*/
@Override
public ImageIcon iconForId(String iconId) {
return iconHelper().iconForId(iconId);
}
/**
* Add an icon listener. Part of the Iconic
* interface.
*
* @param listener The listener.
*/
@Override
public void addIconListener(IconListener listener) {
stateHandler().assertAlive();
iconHelper().addIconListener(listener);
}
/**
* Remove an icon listener. Part of the Iconic
* interface.
*
* @param listener The listener.
*/
@Override
public void removeIconListener(IconListener listener) {
iconHelper().removeIconListener(listener);
}
/**
* When running a job embedded in code, it may be necessary
* to call this method to initialise the job.
*
* @throws JobDestroyedException
*/
public void initialise() throws JobDestroyedException {
stateHandler().assertAlive();
ComponentBoundry.push(logger().getName(), this);
try {
onInitialised();
onConfigured();
}
finally {
ComponentBoundry.pop();
}
}
/**
* When running a job embedded in code, this method should always
* be called to clear up resources.
*
* @throws JobDestroyedException
*/
public void destroy() throws JobDestroyedException {
stateHandler().assertAlive();
ComponentBoundry.push(logger().getName(), this);
try {
onDestroy();
fireDestroyedState();
}
finally {
ComponentBoundry.pop();
}
}
/**
* Subclasses override this method to perform post creation
* initialisation.
*
*/
protected void onInitialised() {
}
/**
* Subclasses override this method to perform post configuration
* initialisation.
*
*/
protected void onConfigured() {
}
/**
* Subclasses override this method to clear up resources. This is
* called by the framework before child elements have been destroyed.
*
*/
protected void onDestroy() { }
/**
* Subclasses must override this to fire the destroyed state.
* This is called by the framework after all child elements
* have been destroyed.
*/
abstract protected void fireDestroyedState();
}