From: Erik Brakkee Date: Sat, 22 Mar 2008 22:47:55 +0000 (+0000) Subject: added the basic for a more modular setup of applications and test code. X-Git-Tag: wamblee-utils-0.7~833 X-Git-Url: http://wamblee.org/gitweb/?a=commitdiff_plain;h=55067e3e504920245e4983d1f64c13caf18354fa;p=utils added the basic for a more modular setup of applications and test code. A system is now separated in subsystems with clearly defined interfaces. This makes it easy to substitute different systems for different environment (such as test) or to construct different behavior. It will avoid the approach with one big beanRefContext.xml will all configuration inside. --- diff --git a/.classpath b/.classpath index 40a31d71..216ffd34 100644 --- a/.classpath +++ b/.classpath @@ -4,6 +4,8 @@ + + diff --git a/mythtv/war/src/main/resources/org.wamblee.mythtv.properties b/mythtv/war/src/main/resources/org.wamblee.mythtv.properties index c7bf5062..85f9a0e8 100644 --- a/mythtv/war/src/main/resources/org.wamblee.mythtv.properties +++ b/mythtv/war/src/main/resources/org.wamblee.mythtv.properties @@ -1,8 +1,8 @@ org.wamblee.mythtv.datasource=jdbc/mythtv -org.wamblee.mythtv.pollinterval=2 -#org.wamblee.mythtv.monitordir=/data/vcr -#org.wamblee.mythtv.linkdir=/data/vcr/links +org.wamblee.mythtv.pollinterval=120 +org.wamblee.mythtv.monitordir=/data/vcr +org.wamblee.mythtv.linkdir=/data/vcr/links -org.wamblee.mythtv.monitordir=/ext/home/erik/java/workspace/utils/mythtv/testdata/input -org.wamblee.mythtv.linkdir=/ext/home/erik/java/workspace/utils/mythtv/testdata/links +#org.wamblee.mythtv.monitordir=/ext/home/erik/java/workspace/utils/mythtv/testdata/input +#org.wamblee.mythtv.linkdir=/ext/home/erik/java/workspace/utils/mythtv/testdata/links diff --git a/pom.xml b/pom.xml index 981a892b..f7ec73e8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ http://wamblee.org support + system hibernate-jpa security socketproxy diff --git a/system/pom.xml b/system/pom.xml new file mode 100644 index 00000000..c70dddc6 --- /dev/null +++ b/system/pom.xml @@ -0,0 +1,24 @@ + + + + org.wamblee + wamblee-utils + 0.2-SNAPSHOT + + + 4.0.0 + org.wamblee + wamblee-system + jar + wamblee.org system + http://wamblee.org + + + commons-logging + commons-logging + + + + diff --git a/system/src/main/java/org/wamblee/system/AbstractSubSystem.java b/system/src/main/java/org/wamblee/system/AbstractSubSystem.java new file mode 100644 index 00000000..0837d5f6 --- /dev/null +++ b/system/src/main/java/org/wamblee/system/AbstractSubSystem.java @@ -0,0 +1,92 @@ +package org.wamblee.system; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract subsystem class making it easy to implement new + * subsystems. + */ +public abstract class AbstractSubSystem implements SubSystem { + + private static final Log LOG = LogFactory.getLog(AbstractSubSystem.class); + + private String _name; + private List _provided; + private List _required; + private Map _running; + + /** + * Constructs the subsystem. + * @param aName Name of the system. + * @param aProvided Provided services. + * @param aRequired Required services. + */ + protected AbstractSubSystem(String aName, ServiceDescriptor[] aProvided, + ServiceDescriptor[] aRequired) { + _name = aName; + _provided = new ArrayList(); + _provided.addAll(Arrays.asList(aProvided)); + _required = new ArrayList(); + _required.addAll(Arrays.asList(aRequired)); + _running = new HashMap(); + } + + @Override + public final String getName() { + return _name; + } + + @Override + public final ServiceDescriptor[] getProvidedServices() { + return _provided.toArray(new ServiceDescriptor[0]); + } + + @Override + public final ServiceDescriptor[] getRequiredServices() { + return _required.toArray(new ServiceDescriptor[0]); + } + + @Override + public final Service[] initialize(String aContext, Service[] aRequiredServices) { + LOG.info("Initializing '" + aContext + "." + _name + "' with " + Arrays.asList(aRequiredServices)); + doInitialize(aContext + "." + getName(), aRequiredServices); + return _running.values().toArray(new Service[0]); + } + + /** + * Must be implemented for initializing the subsystem. + * The implementation must call {@link #addService(Service)} + * for each service that is started. + * @param aRequiredServices Services that are already running + * from other subsystems that may be used. + */ + protected abstract void doInitialize(String aContext, Service[] aRequiredServices); + + /** + * Implementations must call this method to indicate that + * a new service has been started. + * @param aService Service. + */ + protected final void addService(String aContext, Service aService) { + LOG.info(aContext + ": service '" + aService + "' started."); + _running.put(aService.getDescriptor(), aService); + } + + @Override + public Service[] getRunningServices() { + return _running.values().toArray(new Service[0]); + } + + @Override + public String toString() { + return _name; + } + +} diff --git a/system/src/main/java/org/wamblee/system/CompositeSystem.java b/system/src/main/java/org/wamblee/system/CompositeSystem.java new file mode 100644 index 00000000..f3b1bf75 --- /dev/null +++ b/system/src/main/java/org/wamblee/system/CompositeSystem.java @@ -0,0 +1,95 @@ +package org.wamblee.system; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import sun.util.LocaleServiceProviderPool.LocalizedObjectGetter; + +/** + * Composite system consisting of multiple subsystems. + */ +public class CompositeSystem extends AbstractSubSystem { + + private static final Log LOG = LogFactory.getLog(CompositeSystem.class); + + private SubSystem[] _systems; + + /** + * Construcst the composite system. + * @param aName Name of the system. + * @param aSystems Subsystems. + * @param aProvided Provided services of the system. + * @param aRequired Required services by the system. + */ + public CompositeSystem(String aName, SubSystem[] aSystems, + ServiceDescriptor[] aProvided, ServiceDescriptor[] aRequired) { + super(aName, aProvided, aRequired); + _systems = aSystems; + validate(); + } + + /** + * Validates the subsystems together to check that there are + * no required services not in the required list and + * no services in the provided list that cannot be provided. + * Also logs a warning in case of superfluous requirements. + */ + private void validate() { + List provided = new ArrayList(); + for (SubSystem system : _systems) { + provided.addAll(Arrays.asList(system.getProvidedServices())); + } + + List required = new ArrayList(); + for (SubSystem system : _systems) { + required.addAll(Arrays.asList(system.getRequiredServices())); + } + + for (ServiceDescriptor service : getProvidedServices()) { + if (!(provided.contains(service))) { + throw new SystemAssemblyException(getName() + ": Service '" + service + + "' is not provided by any of the subsystems"); + } + } + + for (ServiceDescriptor service : getRequiredServices()) { + if (!(required.contains(service))) { + info("Service '" + + service + + "' indicated as required is not actually required by any of the subsystems"); + } + } + + List reallyRequired = new ArrayList( + required); + for (ServiceDescriptor service : provided) { + reallyRequired.remove(service); + } + for (ServiceDescriptor service: getRequiredServices()) { + reallyRequired.remove(service); + } + for (ServiceDescriptor service: reallyRequired) { + throw new SystemAssemblyException(getName() + ": " + "Service '" + service + "' is not provided internally and is not indicated as required for this sub system"); + } + } + + @Override + protected void doInitialize(String aContext, Service[] aRequiredServices) { + List descriptors = new ArrayList(); + for (Service service : aRequiredServices) { + descriptors.add(service.getDescriptor()); + } + SystemAssembler assembler = new SystemAssembler(getName(), _systems, + descriptors.toArray(new ServiceDescriptor[0])); + assembler.start(aRequiredServices); + } + + private void info(String aMsg) { + LOG.info(getName() + ": " + aMsg); + } + +} diff --git a/system/src/main/java/org/wamblee/system/DefaultService.java b/system/src/main/java/org/wamblee/system/DefaultService.java new file mode 100644 index 00000000..fb2128ee --- /dev/null +++ b/system/src/main/java/org/wamblee/system/DefaultService.java @@ -0,0 +1,35 @@ +package org.wamblee.system; + +/** + * Default service implementation. + */ +public class DefaultService implements Service { + + private ServiceDescriptor _descriptor; + private Object _service; + + /** + * Constructs the service. + * @param aDescriptor Descriptor to use. + * @param aService Service. + */ + public DefaultService(ServiceDescriptor aDescriptor, Object aService) { + _descriptor = aDescriptor; + _service = aService; + } + + @Override + public ServiceDescriptor getDescriptor() { + return _descriptor; + } + + @Override + public T reference(Class aClass) { + return (T)_service; + } + + @Override + public String toString() { + return "(" + _descriptor + ", " + _service + ")"; + } +} diff --git a/system/src/main/java/org/wamblee/system/DefaultServiceDescriptor.java b/system/src/main/java/org/wamblee/system/DefaultServiceDescriptor.java new file mode 100644 index 00000000..82f14565 --- /dev/null +++ b/system/src/main/java/org/wamblee/system/DefaultServiceDescriptor.java @@ -0,0 +1,41 @@ +package org.wamblee.system; + +/** + * Default implementation of a service descriptor. + */ +public class DefaultServiceDescriptor implements ServiceDescriptor { + + private Class _class; + + /** + * Constructs the descriptor. + * @param aClass Type of service. + */ + public DefaultServiceDescriptor(Class aClass) { + _class = aClass; + } + + @Override + public Class getInterfaceType() { + return _class; + } + + @Override + public boolean equals(Object obj) { + if ( !(obj instanceof DefaultServiceDescriptor)) { + return false; + } + DefaultServiceDescriptor descr = (DefaultServiceDescriptor)obj; + return _class.equals(descr._class); + } + + @Override + public int hashCode() { + return _class.hashCode(); + } + + @Override + public String toString() { + return _class.getName().toString(); + } +} diff --git a/system/src/main/java/org/wamblee/system/Service.java b/system/src/main/java/org/wamblee/system/Service.java new file mode 100644 index 00000000..0db48511 --- /dev/null +++ b/system/src/main/java/org/wamblee/system/Service.java @@ -0,0 +1,18 @@ +package org.wamblee.system; + +/** + * Represents a running service. + */ +public interface Service { + /** + * Gets the descriptor of the service. + * @return Descriptor. + */ + ServiceDescriptor getDescriptor(); + + /** + * Returns a reference to the running service. + * @return Service. + */ + T reference(Class aClass); +} diff --git a/system/src/main/java/org/wamblee/system/ServiceDescriptor.java b/system/src/main/java/org/wamblee/system/ServiceDescriptor.java new file mode 100644 index 00000000..793414c3 --- /dev/null +++ b/system/src/main/java/org/wamblee/system/ServiceDescriptor.java @@ -0,0 +1,18 @@ +package org.wamblee.system; + +/** + * Service descriptor providing a description and characteristics + * of the provided service. + * + * NOTE: The current implemention only stores the type of the + * descriptor but his can be extended towards more complex rules + * for matching services. + */ +public interface ServiceDescriptor { + + /** + * Returns the service type. + * @return Service type. + */ + Class getInterfaceType(); +} diff --git a/system/src/main/java/org/wamblee/system/SubSystem.java b/system/src/main/java/org/wamblee/system/SubSystem.java new file mode 100644 index 00000000..9d68ee4e --- /dev/null +++ b/system/src/main/java/org/wamblee/system/SubSystem.java @@ -0,0 +1,46 @@ +package org.wamblee.system; + +/** + * A sub system represents a part of a system that required a + * number of services and provides a number of services. + */ +public interface SubSystem { + + /** + * Gets the name of the subsystem. + * @return Subsystem name. + */ + String getName(); + + /** + * Gets a description of the provided interfaces. + * @return Provided interfaces. + */ + ServiceDescriptor[] getProvidedServices(); + + /** + * Gets a description of the required interfaces. + * @return Required interfaces. + */ + ServiceDescriptor[] getRequiredServices(); + + + /** + * Initialises the subsytem by starting all the services that + * it described as provided. + * @param aContext Unique name for the subsystem. + * @param aRequiredServices Running services from other + * subsystems that are required by this subsystem. + * @return Services that are running in the subsystem. + */ + Service[] initialize(String aContext, Service[] aRequiredServices); + + /** + * Gets the list of running services in the subsystem. + * + * This method may only be called after the + * {@link #initialize(String, Service[])} has been called. + * @return + */ + Service[] getRunningServices(); +} diff --git a/system/src/main/java/org/wamblee/system/SystemAssembler.java b/system/src/main/java/org/wamblee/system/SystemAssembler.java new file mode 100644 index 00000000..3bff0564 --- /dev/null +++ b/system/src/main/java/org/wamblee/system/SystemAssembler.java @@ -0,0 +1,113 @@ +package org.wamblee.system; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Assembler to control multiple subsystems. It makes sure that + * all dependencies are met and controls the order in which systems + * are initialized. + */ +public class SystemAssembler { + + private static final Log LOG = LogFactory.getLog(SystemAssembler.class); + + private static final String ROOT_CONTEXT_NAME = "root"; + private String _context; + private SubSystem[] _systems; + + /** + * Constructs the assembler. + * @param aSystems Systems that must be assembled. + * @param aAvailableServices Available services from other systems + * outside of the systems that this assembler manages. + */ + public SystemAssembler(SubSystem[] aSystems, ServiceDescriptor[] aAvailableServices) { + this(ROOT_CONTEXT_NAME, aSystems, aAvailableServices); + } + + /** + * Constructs the assembler. + * @param aContext Context (unique name) of the assembler. + * @param aSystems Systems that must be assembled. + * @param aAvailableServices Available services from other systems + * outside of the systems that this assembler manages. + */ + public SystemAssembler(String aContext, SubSystem[] aSystems, + ServiceDescriptor[] aAvailableServices) { + _context = aContext; + _systems = aSystems; + validate(aAvailableServices); + } + + /** + * Determines if the systems are ordered appropriately so that all + * dependencies are met. + */ + private void validate(ServiceDescriptor[] aDescriptors) throws SystemAssemblyException { + + List allProvided = new ArrayList(); + for (ServiceDescriptor descriptor: aDescriptors) { + allProvided.add(descriptor); + } + for (SubSystem system : _systems) { + // Check if all required services are already provided by earlier + // systems. + ServiceDescriptor[] required = system.getRequiredServices(); + for (ServiceDescriptor descriptor : required) { + if (!(allProvided.contains(descriptor))) { + throw new SystemAssemblyException( + "Service '" + + descriptor + + "' required by system '" + + system + + "' is not provided by systems that are started earlier"); + } + } + + // add all provided services + ServiceDescriptor[] provided = system.getProvidedServices(); + for (ServiceDescriptor descriptor : provided) { + allProvided.add(descriptor); + } + } + } + + /** + * Starts the subsystems. + * @param aRequiredServices Services that are available from + * other systems that have been started before. + */ + public void start(Service[] aRequiredServices) { + LOG.info("Starting '" + _context + "'"); + Map allProvided = new HashMap(); + + for (Service service : aRequiredServices) { + allProvided.put(service.getDescriptor(), service); + } + for (SubSystem system : _systems) { + ServiceDescriptor[] descriptors = system.getRequiredServices(); + List services = new ArrayList(); + for (ServiceDescriptor descriptor : descriptors) { + Service required = allProvided.get(descriptor); + if (required == null) { + throw new SystemAssemblyException("Service '" + descriptor + + "' required by '" + system + "' is null."); + } + services.add(required); + } + Service[] provided = system.initialize(_context, services + .toArray(new Service[0])); + for (Service service : provided) { + allProvided.put(service.getDescriptor(), service); + } + + } + } + +} diff --git a/system/src/main/java/org/wamblee/system/SystemAssemblyException.java b/system/src/main/java/org/wamblee/system/SystemAssemblyException.java new file mode 100644 index 00000000..80e7db7b --- /dev/null +++ b/system/src/main/java/org/wamblee/system/SystemAssemblyException.java @@ -0,0 +1,24 @@ +package org.wamblee.system; + +/** + * Exception thrown when an error occurs in assembling the systems. + */ +public class SystemAssemblyException extends RuntimeException { + + /** + * Constructs the exception. + * @param aMsg Message. + */ + public SystemAssemblyException(String aMsg) { + super(aMsg); + } + + /** + * Constructs the exception. + * @param aMsg Message + * @param aCause Cause. + */ + public SystemAssemblyException(String aMsg, Throwable aCause) { + super(aMsg, aCause); + } +} diff --git a/system/src/test/java/org/wamblee/system/Application.java b/system/src/test/java/org/wamblee/system/Application.java new file mode 100644 index 00000000..1a725b3c --- /dev/null +++ b/system/src/test/java/org/wamblee/system/Application.java @@ -0,0 +1,21 @@ +package org.wamblee.system; + +import javax.sql.DataSource; + +public class Application extends AbstractSubSystem { + private static final ServiceDescriptor[] REQUIRED = + new ServiceDescriptor[] { + new DefaultServiceDescriptor(DataSource.class), + new DefaultServiceDescriptor(Integer.class) + }; + + public Application() { + super("application", new ServiceDescriptor[0], REQUIRED); + } + + @Override + protected void doInitialize(String aContext, Service[] aRequiredServices) { + // Empty, no services provided externally. + } + +} diff --git a/system/src/test/java/org/wamblee/system/Environment.java b/system/src/test/java/org/wamblee/system/Environment.java new file mode 100644 index 00000000..bd014c5a --- /dev/null +++ b/system/src/test/java/org/wamblee/system/Environment.java @@ -0,0 +1,23 @@ +package org.wamblee.system; + +import javax.sql.DataSource; + + +public class Environment extends AbstractSubSystem { + + private static final ServiceDescriptor[] PROVIDED = + new ServiceDescriptor[] { + new DefaultServiceDescriptor(DataSource.class), + new DefaultServiceDescriptor(Integer.class) + }; + + public Environment() { + super("environment", PROVIDED, new ServiceDescriptor[0]); + } + + @Override + protected void doInitialize(String aContext, Service[] aRequiredServices) { + addService(aContext, new DefaultService(PROVIDED[0], new Integer(1))); + addService(aContext, new DefaultService(PROVIDED[1], new Integer(2))); + } +} diff --git a/system/src/test/java/org/wamblee/system/SystemAssemblerTest.java b/system/src/test/java/org/wamblee/system/SystemAssemblerTest.java new file mode 100644 index 00000000..94bf63c0 --- /dev/null +++ b/system/src/test/java/org/wamblee/system/SystemAssemblerTest.java @@ -0,0 +1,105 @@ +package org.wamblee.system; + +import junit.framework.TestCase; + +public class SystemAssemblerTest extends TestCase { + + public void testEnvironmentApplication() { + SubSystem environment = new Environment(); + SubSystem application = new Application(); + SystemAssembler assembler = new SystemAssembler(new SubSystem[] { + environment, application }, new ServiceDescriptor[0]); + assembler.start(new Service[0]); + Service[] envServices = environment.getRunningServices(); + assertEquals(2, envServices.length); + Service[] appServices = environment.getRunningServices(); + assertEquals(2, appServices.length); + } + + public void testApplicationEnvironment() { + try { + SubSystem environment = new Environment(); + SubSystem application = new Application(); + SystemAssembler assembler = new SystemAssembler(new SubSystem[] { + application, environment }, new ServiceDescriptor[0]); + assembler.start(new Service[0]); + } catch (SystemAssemblyException e) { + // e.printStackTrace(); + return; + } + fail(); + } + + public void testComposite() { + SubSystem environment = new Environment(); + SubSystem application = new Application(); + CompositeSystem system = new CompositeSystem("all", new SubSystem[] { + environment, application }, new ServiceDescriptor[0], + new ServiceDescriptor[0]); + system.initialize("root", new Service[0]); + ServiceDescriptor[] required = system.getRequiredServices(); + assertEquals(0, required.length); + ServiceDescriptor[] provided = system.getProvidedServices(); + assertEquals(0, provided.length); + } + + public void testCompositeWithWrongProvidedInfo() { + try { + SubSystem environment = new Environment(); + SubSystem application = new Application(); + CompositeSystem system = new CompositeSystem("all", + new SubSystem[] { environment, application }, + new ServiceDescriptor[] { new DefaultServiceDescriptor( + String.class) }, new ServiceDescriptor[0]); + } catch (SystemAssemblyException e) { + return; + } + fail(); + } + + public void testCompositeWithSuperfluousRequiredInfo() { + SubSystem environment = new Environment(); + SubSystem application = new Application(); + CompositeSystem system = new CompositeSystem("all", new SubSystem[] { + environment, application }, new ServiceDescriptor[0], + new ServiceDescriptor[] { new DefaultServiceDescriptor( + String.class) }); + system.initialize("root", new Service[0]); + ServiceDescriptor[] required = system.getRequiredServices(); + assertEquals(1, required.length); + ServiceDescriptor[] provided = system.getProvidedServices(); + assertEquals(0, provided.length); + } + + public void testCompositeWithExternalDependencesNotProvided() { + try { + SubSystem environment = new Environment(); + SubSystem application = new Application(); + CompositeSystem system = new CompositeSystem("all", + new SubSystem[] { application }, new ServiceDescriptor[0], + application.getRequiredServices()); + system.initialize("root", new Service[0]); + } catch (SystemAssemblyException e) { + return; + } + fail(); + + } + + public void testCompositeWithExternalDependencesProvided() { + + SubSystem environment = new Environment(); + SubSystem application = new Application(); + CompositeSystem system = new CompositeSystem("all", + new SubSystem[] { application }, new ServiceDescriptor[0], + application.getRequiredServices()); + Service[] envServices = environment.initialize("env", new Service[0]); + system.initialize("root", envServices); + ServiceDescriptor[] required = system.getRequiredServices(); + assertEquals(2, required.length); + ServiceDescriptor[] provided = system.getProvidedServices(); + assertEquals(0, provided.length); + + } + +}