Merge branch 'master' of github.com:trasukg/river-container

Conflicts:
	.gitignore
diff --git a/.gitignore b/.gitignore
index 0f182a0..0c93635 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*/target/**
 *.class
 
 # Package Files #
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..10f4733
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.river</groupId>
+  <artifactId>river-container</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  <name>river-container</name>
+  <modules>
+    <module>river-container-core</module>
+  </modules>
+</project>
\ No newline at end of file
diff --git a/river-container-core/pom.xml b/river-container-core/pom.xml
new file mode 100644
index 0000000..d99b171
--- /dev/null
+++ b/river-container-core/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.river</groupId>
+        <artifactId>river-container</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <groupId>org.apache.river</groupId>
+    <artifactId>river-container-core</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>river-container-core</name>
+    <url>http://maven.apache.org</url>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-vfs2</artifactId>
+            <version>2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.jini</groupId>
+            <artifactId>jsk-platform</artifactId>
+            <version>2.2.1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.jini</groupId>
+            <artifactId>jsk-resources</artifactId>
+            <version>2.2.1</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>javacc-maven-plugin</artifactId>
+                <version>2.6</version>
+                <executions>
+                    <execution>
+                        <id>jjtree-javacc</id>
+                        <goals>
+                            <goal>jjtree-javacc</goal>
+                        </goals>
+                        <configuration>
+                            <!-- options for JJTree and JavaCC go here -->
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jaxb2-maven-plugin</artifactId>
+                <version>1.5</version>
+                <executions>
+                    <execution>
+                        <id>xjc</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <packageName>org.apache.river.container.config</packageName> <!-- The name of your generated source package -->
+                </configuration>
+            </plugin>      
+        </plugins>
+   
+    </build>
+</project>
diff --git a/river-container-core/src/main/java/org/apache/river/container/AbstractConnector.java b/river-container-core/src/main/java/org/apache/river/container/AbstractConnector.java
new file mode 100644
index 0000000..cbe64c1
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/AbstractConnector.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.io.File;
+import java.net.URL;
+
+/**
+ * Defines an abstract connector for the surrogate container.
+ * The connector looks after discovering the device that needs the surrogate,
+ * then loads up the surrogate's jar file and installs the surrogate into the
+ * container.
+ * @author trasukg
+ */
+public class AbstractConnector {
+
+    Host host=null;
+
+    /**
+     * Process the surrogate device's request to host a surrogate, starting
+     * from a URL to the surrogate package.
+     */
+    public void processSurrogateHostingRequest(URL surrogatePackage) {
+        // Allocate a working directory
+        File workingDir=host.newWorkDirectory();
+        
+        SurrogateInstaller installer= (SurrogateInstaller)
+                host.getAttribute(Names.SURROGATE_INSTALLER);
+
+        installer.installSurrogate(host, workingDir);
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/AnnotatedClassDeployer.java b/river-container-core/src/main/java/org/apache/river/container/AnnotatedClassDeployer.java
new file mode 100644
index 0000000..91b3e0c
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/AnnotatedClassDeployer.java
@@ -0,0 +1,518 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.beans.Introspector;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author trasukg
+ */
+public class AnnotatedClassDeployer implements ContextListener {
+
+    private static final Logger log =
+            Logger.getLogger(AnnotatedClassDeployer.class.getName(),
+            MessageNames.BUNDLE_NAME);
+    private Context context = null;
+    private List<DeployedObject> uninitializedObjects =
+            new ArrayList<DeployedObject>();
+    private Map<String, DeployedObject> initializedObjects =
+            new HashMap<String, DeployedObject>();
+    private List<DeploymentListener> deploymentListeners =
+            new ArrayList<DeploymentListener>();
+
+    public List<DeploymentListener> getDeploymentListeners() {
+        return deploymentListeners;
+    }
+
+    public void put(String name, Object o) {
+        try {
+            /*
+            Scan the object's class and register any injection needs from it.
+             */
+            readInObject(name, o);
+            /* Attempt to satisfy the object's injection needs from current
+            candidates.
+             */
+            resolve();
+        } catch (IllegalArgumentException ex) {
+            throw new ConfigurationException(
+                    ex,
+                    MessageNames.ILLEGAL_ARGUMENT_EXCEPTION);
+        } catch (IllegalAccessException ex) {
+            throw new ConfigurationException(
+                    ex,
+                    MessageNames.ILLEGAL_ACCESS_EXCEPTION);
+        } catch (InvocationTargetException ex) {
+            throw new ConfigurationException(
+                    ex,
+                    MessageNames.INVOCATION_TARGET_EXCEPTION);
+        }
+    }
+
+    public void remove(Object o) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public void setContext(Context ctx) {
+        this.context = ctx;
+    }
+
+    /** 
+    When the context notifies us that it is done reading the initialization
+    file, etc, then we can inject default values, check for unresolved
+    dependencies, etc.
+     */
+    public void initComplete() {
+        checkUnresolvedDependencies();
+    }
+
+    @Override
+    public void shutDown() {
+        for (DeploymentListener l : getDeploymentListeners()) {
+            l.shutDown();
+        }
+    }
+
+    private void checkUnresolvedDependencies() {
+        if (uninitializedObjects.isEmpty()) {
+            return;
+        }
+
+        for (DeployedObject depObj : uninitializedObjects) {
+            for (Member m : depObj.getUnresolvedDependencies()) {
+                log.log(Level.SEVERE,
+                        MessageNames.UNRESOLVED_DEPENDENCY,
+                        new Object[]{depObj.getName(),
+                            m.getName(), nameOfRequiredObject(m)});
+            }
+        }
+        throw new ConfigurationException(MessageNames.ANNOTATED_OBJECT_DEPLOYER_HAS_UNRESOLVED_DEPENDENCIES);
+    }
+
+    /**
+    Given an object, see if it is fully resolved (no outstanding dependencies).
+    If it is, then call any initialization that may be required, and move it
+    to our list of 'ready' objects.
+    @param deployed
+    @return True if the list of 'ready' objects has been changed.
+    @throws IllegalAccessException
+    @throws IllegalArgumentException
+    @throws InvocationTargetException
+     */
+    private boolean initializeIfFullyResolved(DeployedObject deployed) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        if (deployed.getUnresolvedDependencies().isEmpty()) {
+            if (deployed.isInitialized() == false) {
+
+                uninitializedObjects.remove(deployed);
+                for (Method initMethod : deployed.getInitMethods()) {
+                    initMethod.invoke(deployed.getDeployedObject());
+                }
+                notifyDeploymentListeners(deployed);
+                initializedObjects.put(deployed.getName(), deployed);
+                if (deployed.getDeployedObject() instanceof DeploymentListener) {
+                    deploymentListeners.add((DeploymentListener) deployed.getDeployedObject());
+                }
+                if (!deployed.getShutdownMethods().isEmpty()) {
+                    deploymentListeners.add(new ShutdownRunner(deployed));
+                }
+                deployed.setInitialized(true);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void notifyDeploymentListeners(DeployedObject deployed) {
+        for (DeploymentListener l : deploymentListeners) {
+            l.postInit(deployed.getName(), deployed.getDeployedObject());
+        }
+    }
+
+    private void readInObject(String name, Object o) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        log.log(Level.FINER,
+                MessageNames.READING_OBJECT,
+                new Object[]{name, o.getClass().getName()});
+
+        /* Get the object's class. */
+        Class cls = o.getClass();
+        DeployedObject deployed = new DeployedObject();
+        deployed.setDeployedObject(o);
+        deployed.setName(name);
+        List<Member> members = buildMemberList(cls);
+        List<Member> unresolved = deployed.getUnresolvedDependencies();
+        log.log(Level.FINER,
+                MessageNames.READING_OBJECT_MEMBER_COUNT, members.size());
+
+        for (Member m : members) {
+            if (isAnnotatedAsInjected(m)) {
+                log.log(Level.FINER,
+                        MessageNames.READING_OBJECT_ANNOTATED_MEMBER_FOUND, m.toString());
+                unresolved.add(m);
+            }
+            if (isInitMethod(m)) {
+                deployed.getInitMethods().add((Method) m);
+            }
+            if (isShutdownMethod(m)) {
+                deployed.getShutdownMethods().add((Method) m);
+            }
+            if (isAnnotatedAsName(m)) {
+                memberSet(deployed.getDeployedObject(), m, deployed.getName());
+            }
+        }
+        /* Add the object
+        and list to our unsatisfied group. If it has no dependencies, then it
+        will be initialized and moved over to the initialized group when
+        resolve() is called.
+         */
+        uninitializedObjects.add(deployed);
+    }
+
+    /**
+    It's an init method if it is
+    <ul>
+    <li>a Method</li>
+    <li>is annotated @Init</li>
+    <li>takes no parameter</li>
+    <li>returns void</li>
+    </ul>
+    @param m The method to evaluate.
+    @return
+     */
+    private boolean isInitMethod(Member m) {
+        AnnotatedElement annEm = (AnnotatedElement) m;
+        if (annEm.getAnnotation(Init.class) == null) {
+            return false;
+        }
+        Method method = (Method) m;
+        if (method.getReturnType() != void.class) {
+            throw new ConfigurationException(MessageNames.INIT_METHOD_NOT_VOID,
+                    m.getDeclaringClass().getName(),
+                    m.getName(),
+                    method.getReturnType());
+        }
+        if (method.getParameterTypes().length != 0) {
+            throw new ConfigurationException(MessageNames.INIT_METHOD_HAS_PARAMETERS,
+                    m.getDeclaringClass().getName(),
+                    m.getName());
+        }
+        return true;
+    }
+
+    /**
+    It's a shutdown method if it is
+    <ul>
+    <li>a Method</li>
+    <li>is annotated @Shutdown</li>
+    <li>takes no parameter</li>
+    <li>returns void</li>
+    </ul>
+    @param m The method to evaluate.
+    @return
+     */
+    private boolean isShutdownMethod(Member m) {
+        AnnotatedElement annEm = (AnnotatedElement) m;
+        if (annEm.getAnnotation(Shutdown.class) == null) {
+            return false;
+        }
+        Method method = (Method) m;
+        if (method.getReturnType() != void.class) {
+            throw new ConfigurationException(MessageNames.SHUTDOWN_METHOD_NOT_VOID,
+                    m.getDeclaringClass().getName(),
+                    m.getName(),
+                    method.getReturnType());
+        }
+        if (method.getParameterTypes().length != 0) {
+            throw new ConfigurationException(MessageNames.SHUTDOWN_METHOD_HAS_PARAMETERS,
+                    m.getDeclaringClass().getName(),
+                    m.getName());
+        }
+        return true;
+    }
+
+    private boolean isAnnotatedAsInjected(Member m) {
+        AnnotatedElement annEm = (AnnotatedElement) m;
+        if (annEm.getAnnotation(Injected.class) != null) {
+            if (m instanceof Field) {
+                return true;
+            }
+            if (m instanceof Method && m.getName().startsWith(Strings.SET)) {
+                return true;
+            } else {
+                throw new ConfigurationException(MessageNames.BAD_MEMBER_FOR_INJECTED_ANNOTATION, m.getDeclaringClass(), m.getName());
+            }
+        }
+        return false;
+    }
+
+    private boolean isAnnotatedAsName(Member m) {
+        AnnotatedElement annEm = (AnnotatedElement) m;
+        if (annEm.getAnnotation(Name.class) != null) {
+            if (m instanceof Field && ((Field) m).getType() == String.class) {
+                return true;
+            }
+            if (m instanceof Method && m.getName().startsWith(Strings.SET)
+                    && ((Method) m).getParameterTypes().length == 1
+                    && ((Method) m).getParameterTypes()[0] == String.class) {
+                return true;
+            } else {
+                throw new ConfigurationException(MessageNames.BAD_MEMBER_FOR_NAME_ANNOTATION, m.getDeclaringClass(), m.getName());
+            }
+        }
+        return false;
+    }
+
+    private boolean isNull(Object o, Member m) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        Object value = null;
+        if (m instanceof Field) {
+            value = ((Field) m).get(o);
+        }
+        if (m instanceof Method) {
+            value = ((Method) m).invoke(o, new Object[0]);
+        }
+        return value == null;
+    }
+
+    /** Build a list of members that might be candidates for injection.
+    
+    @param cls
+    @return
+    @throws SecurityException
+     */
+    List<Member> buildMemberList(Class cls) throws SecurityException {
+        List<Member> members = new ArrayList<Member>();
+        try {
+        members.addAll(Arrays.asList(cls.getDeclaredMethods()));
+        members.addAll(Arrays.asList(cls.getDeclaredFields()));
+        } catch(Error err) {
+            Utils.logClassLoaderHierarchy(log, cls);
+            throw err;
+        }
+        return members;
+    }
+
+    private void removeDependencyFromUnresolvedList(DeployedObject deployed, Member m) {
+        deployed.getUnresolvedDependencies().remove(m);
+    }
+
+    private void resolve() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        boolean changed;
+        do {
+            changed = false;
+            /* For each object in the unsatisfied group, */
+            /* Group of uninitialized objects may change while we're going
+            through them, so use a copy of the array.
+             */
+            for (DeployedObject deployed : new ArrayList<DeployedObject>(uninitializedObjects)) {
+                changed = resolveDeployedObject(deployed);
+            }
+        } while (changed);
+
+    }
+
+    private boolean resolveDeployedObject(DeployedObject deployed) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        /* Attempt to resolve unsatisfied dependencies. */
+        for (Member m : new ArrayList<Member>(deployed.getUnresolvedDependencies())) {
+            Object val = null;
+            InjectionStyle style = injectionStyle(m);
+
+            if (style == InjectionStyle.BY_NAME) {
+                String name = nameOfRequiredObject(m);
+                DeployedObject deployedCandidate=initializedObjects.get(name);
+                if (deployedCandidate != null) {
+                    val=deployedCandidate.getDeployedObject();
+                }
+            } else if (style==InjectionStyle.BY_TYPE) {
+                /* Find a type-compatible object if name lookup fails. */
+                val = findAssignable(m);
+            }
+            if (val != null) {
+                inject(deployed, m, val);
+                removeDependencyFromUnresolvedList(deployed, m);
+            }
+        }
+        /* If satisfied, remove from unsatisfied group and
+        put into candidate group, then initialize. */
+        boolean changed = initializeIfFullyResolved(deployed);
+        return changed;
+    }
+
+    private InjectionStyle injectionStyle(Member m) {
+        AnnotatedElement annEm = (AnnotatedElement) m;
+        Injected inj = (Injected) annEm.getAnnotation(Injected.class);
+        return inj.style() != InjectionStyle.DEFAULT ? inj.style() : InjectionStyle.BY_NAME;
+    }
+
+    private String nameOfRequiredObject(Member m) {
+        AnnotatedElement annEm = (AnnotatedElement) m;
+        Injected inj = (Injected) annEm.getAnnotation(Injected.class);
+        if (inj.value() != null && !Strings.EMPTY.equals(inj.value())) {
+            return inj.value();
+        }
+        if (m instanceof Field) {
+            return ((Field) m).getName();
+        }
+        if (m instanceof Method) {
+            String methodName = m.getName();
+            if (methodName.startsWith(Strings.SET)) {
+                /* Strip off 'set' and decapitalize the remainder according
+                to JavaBeans spec. */
+                return Introspector.decapitalize(methodName.substring(3));
+            }
+        }
+        return null;
+    }
+
+    /**
+    Find an injection candidate object that is assignable to the given member.
+    This candidate must be completely initialized.  As a special case, if the
+    type is assignable from the context object, then the context object will
+    be returned.
+    @param m
+    @return
+     */
+    private Object findAssignable(Member m) {
+        Class requiredType = null;
+        if (m instanceof Method) {
+            requiredType = ((Method) m).getParameterTypes()[0];
+        } else {
+            requiredType = ((Field) m).getType();
+        }
+
+        /* Don't do injection by type on value types or String. */
+        if (isSimpleType(requiredType)) {
+            return null;
+        }
+        if (requiredType.isAssignableFrom(Context.class)) {
+            return context;
+        }
+        for (DeployedObject candidate : initializedObjects.values()) {
+            if (requiredType.isAssignableFrom(candidate.getDeployedObject().getClass())) {
+                return candidate.getDeployedObject();
+            }
+        }
+        return null;
+    }
+    private Class[] simpleTypes = {
+        boolean.class,
+        int.class,
+        long.class,
+        float.class,
+        double.class,
+        Boolean.class,
+        Integer.class,
+        Long.class,
+        Float.class,
+        Double.class,
+        java.util.Date.class,
+        String.class
+    };
+
+    private boolean isSimpleType(Class type) {
+        for (Class simpleType : simpleTypes) {
+            if (type == simpleType) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Inject a resolved value into the deployed object.
+    After the injection is complete, remove the member from our list of
+    unresolved dependencies for this object.  If there are no further
+    unresolved dependencies, call the object's init method and move it into
+    the initialized objects group.
+    
+    @param deployed The holder for the deployment unit.
+    @param m The member (either Field or Method that is used to set the value.
+    @param val The value to set.
+     */
+    private void inject(DeployedObject deployed, Member m, Object val) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        log.log(Level.FINER,
+                MessageNames.INJECT,
+                new Object[]{deployed.getName(), m.getName(), val});
+        memberSet(deployed.getDeployedObject(), m, val);
+    }
+
+    private void memberSet(Object target, Member member, Object value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        if (member instanceof Field) {
+            Field f = (Field) member;
+            boolean originalAccess = f.isAccessible();
+            f.setAccessible(true);
+            f.set(target, convertProperty(value, f.getType()));
+            f.setAccessible(originalAccess);
+        } else {
+            Method m = (Method) member;
+            boolean originalAccess = m.isAccessible();
+            m.setAccessible(true);
+            m.invoke(target, convertProperty(value, m.getParameterTypes()[0]));
+            m.setAccessible(originalAccess);
+        }
+    }
+
+    private Object convertProperty(Object value, Class requiredType) {
+        /* TODO: Make this a little more flexible and configurable! */
+        if (requiredType.isAssignableFrom(value.getClass())) {
+            return value;
+        }
+        if (requiredType.equals(Integer.class)
+                || requiredType.equals(int.class)) {
+            return Integer.parseInt(value.toString());
+        }
+        throw new LocalizedRuntimeException(MessageNames.BUNDLE_NAME, MessageNames.CANT_CONVERT_EXCEPTION,
+                new Object[]{value.toString(), requiredType.toString()});
+    }
+
+    private class ShutdownRunner implements DeploymentListener {
+
+        private DeployedObject deployedObject = null;
+
+        ShutdownRunner(DeployedObject o) {
+            deployedObject = o;
+        }
+
+        @Override
+        public void postInit(String name, Object object) {
+            // No action.  Handled in upper level.
+        }
+
+        @Override
+        public void shutDown() {
+            for (Method m : deployedObject.getShutdownMethods()) {
+                try {
+                    m.invoke(deployedObject.getDeployedObject(), new Object[0]);
+                } catch (Throwable t) {
+                    // TODO: Figure out what to do here!
+                    t.printStackTrace();
+                }
+            }
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/AttributeStore.java b/river-container-core/src/main/java/org/apache/river/container/AttributeStore.java
new file mode 100644
index 0000000..ee0bfd9
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/AttributeStore.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+/**
+ Implements an attribute store with some features of an IOC container, plus
+ some security visibility features.
+ * @author trasukg
+ */
+public interface AttributeStore {
+    /**
+     Return the attribute listed under the desired name, assuming that
+     the current code has permission to access that attribute.
+     
+     @param name
+     @return The attribute.
+     */
+    public Object getAttribute(String name);
+
+    /**
+     Store an attribute in the attribute store.
+
+     @param name
+     @param attribute
+     */
+    public void setAttribute(String name, Object attribute);
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/AttributeStoreImpl.java b/river-container-core/src/main/java/org/apache/river/container/AttributeStoreImpl.java
new file mode 100644
index 0000000..14cc93d
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/AttributeStoreImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.river.container;
+
+import java.security.GuardedObject;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author trasukg
+ */
+public class AttributeStoreImpl implements AttributeStore {
+
+    private Map<String, Object> attributes=new HashMap<String, Object>();
+
+    /**
+     Retrieve an attribute, checking security permissions if it happens
+     to be a guarded object.
+     @param name
+     @return
+     */
+    public Object getAttribute(String name) {
+        Object attr=attributes.get(name);
+        if (attr instanceof GuardedObject) {
+            return ((GuardedObject) attr).getObject();
+        }
+        return attr;
+    }
+
+    public void setAttribute(String name, Object attribute) {
+        attributes.put(name, attribute);
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Bootstrap.java b/river-container-core/src/main/java/org/apache/river/container/Bootstrap.java
new file mode 100644
index 0000000..d5a2fed
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Bootstrap.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import org.apache.river.container.config.*;
+import org.xml.sax.SAXException;
+
+/**
+ Bootstrap loader for the container. Performs roughly the following: <ul>
+ <li>Based on the configuration parameter fed in at the command line, determine
+ the configuration directory and the config file.</li> <li>Read the
+ configuration file</li> <li>Based on the classpath declared in the config file,
+ create the container's classloader.</li> <li>Using that classloader, create the
+ context.</li> <li>Load any command-line parameters into the context</li>
+ <li>Create all the elements (beans, discovery sets, etc) that are called out in
+ the config file and put them into the context. This will cause those beans to
+ setup and initialize themselves.</li> </li>
+
+ @author trasukg
+ */
+public class Bootstrap {
+
+    private static final Logger log =
+            Logger.getLogger(Bootstrap.class.getName(), MessageNames.BUNDLE_NAME);
+
+    public static void main(String args[]) {
+        try {
+            initializeContainer(args);
+        } catch (InvocationTargetException ex) {
+            log.log(Level.SEVERE, MessageNames.INITIALIZATION_EXCEPTION, ex.getCause());
+            ex.printStackTrace();
+            System.exit(-1);
+        } catch (Exception ex) {
+            log.log(Level.SEVERE, MessageNames.INITIALIZATION_EXCEPTION, ex);
+            ex.printStackTrace();
+            System.exit(-1);
+        }
+    }
+
+    static Unmarshaller createConfigUnmarshaller() throws SAXException, JAXBException {
+        JAXBContext ctx = JAXBContext.newInstance("org.apache.river.container.config");
+        Unmarshaller um = ctx.createUnmarshaller();
+        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        Source source = new StreamSource(Bootstrap.class.getResourceAsStream("/schemas/config.xsd"));
+        Schema schema = sf.newSchema(source);
+        um.setSchema(schema);
+        return um;
+    }
+
+    static URL[] findClasspathURLS(String classpathStr) throws MalformedURLException {
+        StringTokenizer tok = new StringTokenizer(classpathStr, Strings.WHITESPACE_SEPARATORS);
+        List<URL> pathElements = new ArrayList<URL>();
+        while (tok.hasMoreTokens()) {
+            File f = new File(tok.nextToken());
+            pathElements.add(f.toURI().toURL());
+        }
+        URL[] urls = (URL[]) pathElements.toArray(new URL[0]);
+        return urls;
+    }
+
+    private static Map<String, ClassLoader> createClassLoaders(ContainerConfig config) throws MalformedURLException {
+
+        Map<String, ClassLoader> classLoaders = new HashMap<String, ClassLoader>();
+        classLoaders.put(Strings.BOOTSTRAP_CLASS_LOADER, Bootstrap.class.getClassLoader());
+        /*
+         Setup the classloaders according to the config file.
+         */
+        List<String> seen = new LinkedList<String>();
+        Map<String, Classpath> classpaths = new HashMap<String, Classpath>();
+        for (Classpath classpath : config.getClasspath()) {
+            if (classpaths.containsKey(classpath.getId())) {
+                throw new ConfigurationException(MessageNames.DUPLICATE_CLASSPATH, classpath.getId());
+            }
+            classpaths.put(classpath.getId(), classpath);
+        }
+
+        for (String id : classpaths.keySet()) {
+            resolveClassLoader(classLoaders, seen, classpaths, id);
+        }
+        return classLoaders;
+    }
+
+    private static ClassLoader resolveClassLoader(Map<String, ClassLoader> classLoaders,
+            List<String> seen,
+            Map<String, Classpath> classpaths,
+            String id) throws MalformedURLException {
+        if (classLoaders.containsKey(id ) ){
+            return classLoaders.get(id);
+        }
+        if (seen.contains(id)) {
+            throw new ConfigurationException(MessageNames.CIRCULAR_CLASSPATH, id);          
+        }
+        // Add the id to the list of classloaders we have attempted to build.
+        seen.add(id);
+        Classpath classpath=classpaths.get(id);
+        if (classpath==null) {
+            throw new ConfigurationException(MessageNames.CLASSPATH_UNDEFINED, id);
+        }
+        String parentClasspathId=classpath.getParent();
+        ClassLoader parentClassLoader=null;
+        if (parentClasspathId!=null && !Strings.EMPTY.equals(parentClasspathId) ){
+            parentClassLoader=resolveClassLoader(classLoaders, seen, classpaths, parentClasspathId);
+        } else {
+            /* Should be the 'extension' classloader. */
+            parentClassLoader=Bootstrap.class.getClassLoader().getParent();
+        }
+        URL[] classpathUrls;
+        classpathUrls = findClasspathURLS(classpath.getValue());
+        
+        SettableCodebaseClassLoader classLoader = new SettableCodebaseClassLoader(classpathUrls,
+                parentClassLoader);
+        classLoaders.put(id, classLoader);
+        log.log(Level.FINE, MessageNames.CONFIGURED_CLASSPATH, new Object[]{
+                    id,
+                    Utils.format(classpathUrls)});
+        seen.remove(id);
+        return classLoader;      
+    }
+
+    static void initializeContainer(String args[]) throws SAXException, JAXBException, FileNotFoundException, MalformedURLException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, ConfigurationException, Exception {
+        //Logger.getLogger("org.apache.river.container.AnnotatedClassDeployer").setLevel(Level.ALL);
+        Logger.getLogger(CommandLineArgumentParser.class.getName()).setLevel(Level.ALL);
+
+        ContainerConfig coreConfig = readCoreConfig();
+        Map<String, ClassLoader> classLoaders = createClassLoaders(coreConfig);
+
+        ClassLoader containerClassLoader = classLoaders.get(Strings.CONTAINER_CLASS_LOADER);
+
+        /*
+         Create the context object.
+         */
+        Object context = Class.forName(Strings.CONTEXT_CLASS, true, containerClassLoader).newInstance();
+        Method putByNameMethod = context.getClass().getMethod(
+                Strings.PUT, new Class[]{String.class, Object.class});
+        Method initCompleteMethod = context.getClass().getMethod(Strings.INIT_COMPLETE, new Class[0]);
+        Thread.currentThread().setContextClassLoader(containerClassLoader);
+        putByNameMethod.invoke(context, Strings.CLASS_LOADERS, (Object) classLoaders);
+        /*
+         Process the core configuration
+         */
+        processConfiguration(coreConfig, containerClassLoader, context);
+        /*
+         We need to set the command line args after processing the core
+         configuration so that the items in the core-config get initialized.
+         */
+        putByNameMethod.invoke(context, Strings.COMMAND_LINE_ARGS, (Object) args);
+        /*
+         The core configuration now loads the profile configuration...
+         */
+        // processConfiguration(containerConfig, classLoader, putMethod, context, putByNameMethod);
+        initCompleteMethod.invoke(context, new Object[0]);
+    }
+
+    static void processConfiguration(ContainerConfig config, Object classLoader, Object context) throws InvocationTargetException, ConfigurationException, IllegalArgumentException, InstantiationException, ClassNotFoundException, IllegalAccessException, NoSuchMethodException, MalformedURLException, Exception {
+        Method putMethod = context.getClass().getMethod(Strings.PUT, new Class[]{Object.class});
+        Method putByNameMethod = context.getClass().getMethod(
+                Strings.PUT, new Class[]{String.class, Object.class});
+
+        /*
+         Add the classpath urls found in the configuration into the classloader.
+         Note that we have to do this by reflection because the classloader
+         we're handed may not be the same classloader that loaded this instance
+         of the Bootstrap class. In particular, this occurs when we are loading
+         the profile configuration by calling out ProfileConfigReader in
+         core-config.xml. In that case, the container classloader is created by
+         the original Bootstrap class inside the original classloader, but
+         ProfileConfigReader and hence another Bootstrap class gets loaded by
+         the container class loader.
+         */
+        /*
+         Not really sure about this.... would be required if we wanted the
+         profile configs to add jar files to the classpath. Not sure if that is
+         really "on", seeing as how we don't really want users attempting to
+         extend the container. Having said that, it's possible that certain
+         deployers might be in a different project, hence different jar files,
+         so we might want to let the profiles add to the classpath. Needs more
+         thought. for (URL url : findClasspathURLS(config)) { new
+         Statement(classLoader, Strings.ADD_URL, new Object[]{url}).execute(); }
+         */
+        for (Object element : config.getElements()) {
+            processElement(element, (ClassLoader) classLoader, putMethod, context, putByNameMethod);
+        }
+    }
+
+    private static ContainerConfig readCoreConfig() throws SAXException, JAXBException, FileNotFoundException {
+        Unmarshaller um = createConfigUnmarshaller();
+        InputStream is = Bootstrap.class.getResourceAsStream(Strings.CORE_CONFIG_XML);
+        ContainerConfig containerConfig = (ContainerConfig) um.unmarshal(is);
+        return containerConfig;
+    }
+
+    private static void processElement(Object element, ClassLoader classLoader, Method putMethod, Object context, Method putByNameMethod) throws ClassNotFoundException, InstantiationException, InvocationTargetException, ConfigurationException, IllegalAccessException, IllegalArgumentException {
+        if (element instanceof Component) {
+            Component c = (Component) element;
+            Class compClass = Class.forName(c.getClazz(), true, classLoader);
+            String name = c.getName();
+            Object instance = compClass.newInstance();
+            if (name == null || name.trim().length() == 0) {
+                putMethod.invoke(context, instance);
+            } else {
+                putByNameMethod.invoke(context, name, instance);
+            }
+        } else if (element instanceof Property) {
+            Property p = (Property) element;
+            putByNameMethod.invoke(context, p.getName(), p.getValue());
+        } else if (element instanceof DiscoveryContextType) {
+            /*
+             Just drop the element into the context under the appropriate name.
+             */
+            DiscoveryContextType dct = (DiscoveryContextType) element;
+            if (dct.getName() == null) {
+                putByNameMethod.invoke(context, Strings.DEFAULT_DISCOVERY_CONTEXT, dct);
+            } else {
+                putByNameMethod.invoke(context, dct.getName(), dct);
+            }
+        } else {
+            throw new ConfigurationException(MessageNames.UNSUPPORTED_ELEMENT, element.getClass().getName());
+        }
+    }
+    /*
+     static URL[] findClasspathURLS(ContainerConfig containerConfig) throws
+     MalformedURLException { String classpathStr =
+     containerConfig.getClasspath(); URL[] urls =
+     findClasspathURLS(classpathStr); return urls; }
+     */
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/CommandLineArgumentParser.java b/river-container-core/src/main/java/org/apache/river/container/CommandLineArgumentParser.java
new file mode 100644
index 0000000..19a0356
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/CommandLineArgumentParser.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ Processor that sets up various context items according to the command line
+ arguments supplied.
+ <p>
+ For one thing, this sets up the profile according to the first command-line
+ parameter.  If the first parameter is there and does not start with a '-',
+ it is used as the profile name.  Otherwise the profile name is set to
+ 'default'.
+ 
+ * @author trasukg
+ */
+public class CommandLineArgumentParser {
+    private static final Logger log=
+            Logger.getLogger(CommandLineArgumentParser.class.getName(),
+            MessageNames.BUNDLE_NAME);
+
+    @Injected
+    public String[] commandLineArguments=null;
+
+    @Injected(style= InjectionStyle.BY_TYPE)
+    Context context=null;
+
+    @Init
+    public void init() {
+        /* If there is a first argument, and it doesn't begin with '-', then
+        it's a profile name.
+         */
+        String cmdString=Utils.format(commandLineArguments);
+        log.log(Level.FINE, MessageNames.SHOW_COMMAND_LINE_ARGUMENTS, cmdString);
+        if (commandLineArguments.length > 0 && !commandLineArguments[0].startsWith(Strings.DASH)) {
+            String profileName = commandLineArguments[0];
+            context.put(Strings.PROFILE, profileName);
+        } else {
+            context.put(Strings.PROFILE, Strings.DEFAULT);
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ConfigurationException.java b/river-container-core/src/main/java/org/apache/river/container/ConfigurationException.java
new file mode 100644
index 0000000..d743bfe
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ConfigurationException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+/**
+ * This exception indicates that an error was found in reading a configuration
+ file.
+ * @author trasukg
+ */
+public class ConfigurationException extends LocalizedRuntimeException {
+   public ConfigurationException(String messageKey, Object ... parameters) {
+       super(MessageNames.BUNDLE_NAME, messageKey, parameters);
+   }
+
+   public ConfigurationException(Throwable rootCause, String messageKey, Object ... parameters) {
+       super(rootCause, MessageNames.BUNDLE_NAME, messageKey, parameters);
+   }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Context.java b/river-container-core/src/main/java/org/apache/river/container/Context.java
new file mode 100644
index 0000000..bbf2de6
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Context.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author trasukg
+ */
+public class Context {
+    Map<String, Object> contents=new HashMap<String, Object>();
+    List<ContextListener> listeners=new ArrayList<ContextListener>();
+
+    /**
+     Put an object into the context.  Object will be indexed under its
+     fully qualified class name.
+     @param o
+     */
+    public void put(Object o) {
+        put(o.getClass().getName(), o);
+    }
+
+    public void put(String name, Object o) {
+        contents.put(name, o);
+        if (o instanceof ContextListener) {
+            ContextListener l=(ContextListener) o;
+            l.setContext(this);
+            listeners.add(l);
+        }
+        /*
+         If the added object happens to implement ContextListener, it
+         will be notified that it was added to the context.
+         */
+        for (ContextListener l: new ArrayList<ContextListener>(listeners)) {
+            l.put(name, o);
+        }
+    }
+
+    /** 
+    Retrieve an object from the context.
+    @param name Name of the object.
+    @return 
+    */
+    public Object get(String name) {
+        return contents.get(name);
+    }
+    
+    /**
+     Called by the bootstrapper to tell us that processing of the initialization
+     file is now complete.
+     */
+    public void initComplete() {
+        for (ContextListener l: listeners) {
+            l.initComplete();
+        }
+
+    }
+    
+    public void shutDown() {
+        for(ContextListener l:listeners) {
+            l.shutDown();
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ContextListener.java b/river-container-core/src/main/java/org/apache/river/container/ContextListener.java
new file mode 100644
index 0000000..243374f
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ContextListener.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface ContextListener {
+
+    /**
+     Indicates that an object has been added to the context.
+     @param name
+     @param o
+     */
+    public void put(String name, Object o);
+
+    /**
+     Indicates that an object has been removed from the context.
+     @param o
+     */
+    public void remove(Object o);
+
+    /**
+     Informs the listener of the context object.
+     @param ctx
+     */
+    public void setContext(Context ctx);
+
+    /**
+     Indicates that processing of any initialization file is complete.
+     */
+    public void initComplete();
+    
+    /**
+    Indicates that the container is shutting down, so all resources should
+    be closed and/or released.
+    */
+    public void shutDown();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/DeployedObject.java b/river-container-core/src/main/java/org/apache/river/container/DeployedObject.java
new file mode 100644
index 0000000..cf58b15
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/DeployedObject.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ Holds information about an object that is being managed by the
+ AnnotatedClassDeployer.
+ * @author trasukg
+ */
+public class DeployedObject {
+    private Object deployedObject=null;
+    private String name;
+    private List<Member> unresolvedDependencies=new ArrayList<Member>();
+    private List<Method> initMethods=new ArrayList<Method>();
+    private List<Method> shutdownMethods=new ArrayList<Method>();
+
+    public List<Method> getShutdownMethods() {
+        return shutdownMethods;
+    }
+
+    private boolean initialized=false;
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public void setInitialized(boolean isInitialized) {
+        this.initialized = isInitialized;
+    }
+    
+    public List<Method> getInitMethods() {
+        return initMethods;
+    }
+
+    public Object getDeployedObject() {
+        return deployedObject;
+    }
+
+    public void setDeployedObject(Object deployedObject) {
+        this.deployedObject = deployedObject;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<Member> getUnresolvedDependencies() {
+        return unresolvedDependencies;
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/DeploymentListener.java b/river-container-core/src/main/java/org/apache/river/container/DeploymentListener.java
new file mode 100644
index 0000000..2e9cf91
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/DeploymentListener.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface DeploymentListener {
+    public void postInit(String name, Object object);
+    
+    public void shutDown();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/FileUtility.java b/river-container-core/src/main/java/org/apache/river/container/FileUtility.java
new file mode 100644
index 0000000..aa3dfbf
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/FileUtility.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.io.IOException;
+import org.apache.commons.vfs2.FileObject;
+
+/**
+ * Utilities for dealing with the files and file systems that the
+ River Container uses.  This class builds on Apache Commons-vfs and uses
+ many of its facilities and classes.
+ * @author trasukg
+ */
+public interface FileUtility {
+    /**
+     Get a working directory with the given name.
+     @param name
+     @return
+     @throws IOException
+     */
+    public FileObject getWorkingDirectory(String name) throws IOException;
+
+    /**
+     Get the 'home' directory for the current container profile.
+     @return
+     @throws IOException
+     */
+    public FileObject getProfileDirectory() throws IOException;
+
+    public FileObject getLibDirectory() throws IOException;
+
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/FileUtilityImpl.java b/river-container-core/src/main/java/org/apache/river/container/FileUtilityImpl.java
new file mode 100644
index 0000000..7b88b19
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/FileUtilityImpl.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.VFS;
+
+/**
+ *
+ * @author trasukg
+ */
+public class FileUtilityImpl implements FileUtility {
+
+    @Injected
+    private String profile=null;
+
+    private FileSystemManager fsm=null;
+
+    public FileObject getWorkingDirectory(String name) throws IOException {
+
+        FileObject workDir=getProfileDirectory().resolveFile(Strings.WORK).resolveFile(name);
+        if (!workDir.exists()) {
+            workDir.createFolder();
+        }
+        return workDir;
+    }
+
+    public FileObject getProfileDirectory() throws IOException {
+        FileObject profileDir = fsm.resolveFile(new File(Strings.PROFILE), profile);
+        return profileDir;
+    }
+
+    @Init
+    public void init() throws FileSystemException {
+        fsm=VFS.getManager();
+    }
+
+    @Override
+    public FileObject getLibDirectory() throws IOException {
+        FileObject libDir = fsm.resolveFile(new File(Strings.LIB), ".");
+        return libDir;
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Host.java b/river-container-core/src/main/java/org/apache/river/container/Host.java
new file mode 100644
index 0000000..c5ce6b1
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Host.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import org.apache.river.container.deployer.ApplicationEnvironment;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is from the early work on the surrogate host and was meant to be
+ the overall container.  At this point, it probably should be refactored or
+ removed and replaced with something that supports the Context-based deployers.
+
+ * @author trasukg
+ */
+public class Host {
+
+    List<ApplicationEnvironment> applications = new ArrayList();
+
+    AttributeStore attributeStore=new AttributeStoreImpl();
+
+    public AttributeStore getAttributeStore() {
+        return attributeStore;
+    }
+
+    public List<ApplicationEnvironment> getApplications() {
+        return applications;
+    }
+
+    File newWorkDirectory() {
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    /**
+     Create a new application environment and add it to the applications
+     list in the Host.
+     @return The newly-created ApplicationEnvironment.
+     */
+    public ApplicationEnvironment newApplicationEnvironment() {
+        ApplicationEnvironment appEnv=new ApplicationEnvironment();
+        applications.add(appEnv);
+        return appEnv;
+    }
+
+    /**
+     Looks up an attribute in the attribute set.
+     @param name
+     @return
+     */
+    public Object getAttribute(String name) {
+        return getAttributeStore().getAttribute(name);
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Init.java b/river-container-core/src/main/java/org/apache/river/container/Init.java
new file mode 100644
index 0000000..4dbe1a4
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Init.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Identifies a method which should be called when all the @Injected
+ dependencies have been satisfied, to allow the target to initialize itself.
+ If the method throws any exception when called, the target will
+ not be available for
+ injection into any other object.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Init {
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Injected.java b/river-container-core/src/main/java/org/apache/river/container/Injected.java
new file mode 100644
index 0000000..3c6d07a
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Injected.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ <p>
+ Annotation to indicate that the contents of the annotated field should
+ be injected by the AnnotatedClassDeployer when the appropriate reference
+ becomes available.
+ </p>
+
+ <p>
+ By default, the injection is done by type; in other words the value is set
+ to the first thing in the context that is assignable to the target field.
+ If the annotation includes the 'name' attribute, then the injection is done
+ by name; the value is set to whatever is stored under that name in the context.
+ </p>
+
+ <p>
+ If the type of the target happens to be Context, then the context itself
+ will be injected.
+ </p>
+
+ <p>
+ An object in the context will not be injected into any other object until
+ it has been fully resolved and its initialization method has been called.
+ Nonetheless, the target object should not do anything with the injected
+ resource as part of the 'setter' method; it should initialize itself inside
+ a method flagged with the @Init annotation, which will be called when all the
+ @Injected fields/methods have been satisfied.
+ </p>
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Injected {
+    String value() default Strings.EMPTY;
+    InjectionStyle style() default InjectionStyle.DEFAULT;
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/InjectionStyle.java b/river-container-core/src/main/java/org/apache/river/container/InjectionStyle.java
new file mode 100644
index 0000000..ee2c2cf
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/InjectionStyle.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+/**
+ *
+ * @author trasukg
+ */
+public enum InjectionStyle {
+    BY_NAME, BY_TYPE, DEFAULT;
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/JarFilter.java b/river-container-core/src/main/java/org/apache/river/container/JarFilter.java
new file mode 100644
index 0000000..3bb34b4
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/JarFilter.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.io.File;
+import java.io.FileFilter;
+
+/**
+ *
+ * @author trasukg
+ */
+public class JarFilter implements FileFilter {
+
+    public boolean accept(File file) {
+        if (!file.isFile()) {
+            return false;
+        }
+        if (!file.canRead()) {
+            return false;
+        }
+        String name = file.getName();
+        if (!name.endsWith(".jar")) {
+            return false;
+        }
+        return true;
+    }
+}
+
diff --git a/river-container-core/src/main/java/org/apache/river/container/LocalizedRuntimeException.java b/river-container-core/src/main/java/org/apache/river/container/LocalizedRuntimeException.java
new file mode 100644
index 0000000..8c11e06
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/LocalizedRuntimeException.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * This is a runtime exception with localized error messages.
+ * @author trasukg
+ */
+public class LocalizedRuntimeException extends RuntimeException {
+    private String messageBundleName;
+    private String messageKey;
+
+    public String getMessageBundleName() {
+        return messageBundleName;
+    }
+
+    public String getMessageKey() {
+        return messageKey;
+    }
+
+    public Object[] getMessageParameters() {
+        return messageParameters;
+    }
+    private Object[] messageParameters;
+
+    /**
+     Construct a runtime exception with a localized message.
+     @param messageBundleName
+     @param messageKey
+     @param messageParameters
+     */
+    public LocalizedRuntimeException(String messageBundleName,
+            String messageKey,
+            Object[] messageParameters) {
+        this.messageBundleName=messageBundleName;
+        this.messageKey=messageKey;
+        this.messageParameters=messageParameters;
+    }
+
+    /**
+     Construct a runtime exception with a localized message.
+     @param messageBundleName
+     @param messageKey
+     @param messageParameters
+     */
+    public LocalizedRuntimeException(Throwable rootCause,
+            String messageBundleName,
+            String messageKey,
+            Object ... messageParameters) {
+        super(rootCause);
+        this.messageBundleName=messageBundleName;
+        this.messageKey=messageKey;
+        this.messageParameters=messageParameters;
+    }
+
+    /**
+     Localize and return the error message, according to the default Locale.
+     Note that the resolution of the resource bundle and the localization is
+     deferred until this method is called, allowing the existence of different
+     resource bundles or locales in the thrower and receiver (e.g. in the case
+     of a serialized exception passed between two JVMs.
+
+     @return The localized message.
+     */
+    @Override
+    public String getMessage() {
+        ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName);
+        String message=(String) bundle.getObject(messageKey);
+        return MessageFormat.format(message, messageParameters);
+    }
+
+    /**
+     Return the message localized using the given locale.
+     Note that the resolution of the resource bundle and the localization is
+     deferred until this method is called, allowing the existence of different
+     resource bundles or locales in the thrower and receiver (e.g. in the case
+     of a serialized exception passed between two JVMs.
+
+     @param locale
+     @return
+     */
+    public String getMessage(Locale locale) {
+        ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName, locale);
+        String message=(String) bundle.getObject(messageKey);
+        return MessageFormat.format(message, messageParameters);
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/MBeanRegistrar.java b/river-container-core/src/main/java/org/apache/river/container/MBeanRegistrar.java
new file mode 100644
index 0000000..8d87ba3
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/MBeanRegistrar.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.lang.management.ManagementFactory;
+import java.util.Hashtable;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+/**
+ *
+ * @author trasukg
+ */
+public class MBeanRegistrar implements DeploymentListener {
+
+    private MBeanServer mbeanServer=null;
+
+    public MBeanServer getMbeanServer() {
+        return mbeanServer;
+    }
+
+    public void postInit(String name, Object object) {
+        try {
+            /*
+             Just try to register it.  If it fails, that's OK.
+             */
+            Hashtable<String,String> props=new Hashtable<String, String>();
+            props.put(Strings.NAME, name);
+            ObjectName objectName=new ObjectName(Strings.CONTAINER_JMX_DOMAIN, props);
+            mbeanServer.registerMBean(object, objectName);
+        } catch(Exception ex) {
+            // Don't really care.
+        }
+    }
+
+    @Init
+    public void init() {
+        mbeanServer=ManagementFactory.getPlatformMBeanServer();
+    }
+
+    @Override
+    public void shutDown() {
+        
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/MessageNames.java b/river-container-core/src/main/java/org/apache/river/container/MessageNames.java
new file mode 100644
index 0000000..bc4ff9f
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/MessageNames.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+/*
+ TODO: Should the messages be separated into different domains based on the
+ audience?  i.e. Should we have ExceptionMessages that might be seen by users
+ and LogMessages that are primarily meant to be seen by developers? UIMessages
+ that are assumed to be used in the UI?
+
+ Advantages:
+ - Possibly better organization
+ - Internationalization/Translation efforts could be more focused.
+
+ Also, should we assign a code to each message (msg number, etc)?  That way,
+ support tools could search an email message for the exception code and
+ then generate a list of candidate support messages.
+ */
+
+/**
+ * Constants that hold message names used in the message resource bundle.
+ * @author trasukg
+ */
+public class MessageNames {
+    public static final String BUNDLE_NAME="org.apache.river.container.Messages";
+
+    public static final String
+            ADDED_PLATFORM_CODEBASE_JAR="addedPlatformCodebaseJar",
+            ADDING_CLASSPATH_ENTRY="addingClasspathEntry",
+            ANNOTATED_OBJECT_DEPLOYER_HAS_UNRESOLVED_DEPENDENCIES="annotatedObjectDeployerHasUnresolvedDependencies",
+            BAD_CLASSPATH_EXPR="badClasspathExpression",
+            BAD_MEMBER_FOR_INJECTED_ANNOTATION="badMemberForInjectedAnnotation",
+            BAD_MEMBER_FOR_NAME_ANNOTATION="badMemberForNameAnnotation",
+            BASIC_WORK_MANAGER_INITIALIZED="basicWorkManagerInitialized",
+            CALLING_MAIN="callingMain",
+            CANT_CONVERT_EXCEPTION="cantConvertException",
+            CANT_READ_START_PROPERTIES="cantReadStartProperties",
+            CIRCULAR_CLASSPATH="circularClasspath",
+            CLASSLOADER_IS="classLoaderIs",
+            CLASSPATH_UNDEFINED="classpathUndefined",
+            CLASS_SERVER_BAD_REQUEST="classServerBadRequest",
+            CLASS_SERVER_ERROR_ACCEPTING_CONNECTIONS="classServerErrorAcceptingConnections",
+            CLASS_SERVER_ESTABLISHED="classServerEstablished",
+            CLASS_SERVER_EXCEPTION_DURING_SHUTDOWN="classServerExceptionDuringShutdown",
+            CLASS_SERVER_EXCEPTION_GETTING_BYTES="classServerExceptionGettingBytes",
+            CLASS_SERVER_EXCEPTION_WRITING_RESPONSE="classServerExceptionWritingResponse",
+            CLASS_SERVER_INIT_FAILED="classServerInitFailed",
+            CLASS_SERVER_NO_CONTENT_FOUND="classServerNoContentFound",
+            CLASS_SERVER_RECEIVED_REQUEST="classServerReceivedRequest",
+            CLASS_SERVER_RECEIVED_PROBE="classServerReceivedProbe",    
+            CLASS_SERVER_REJECTED_PATH="classServerRejectedPath",
+            CLASS_SERVER_TERMINATED="classServerTerminated",
+            CODESOURCE_IS="codeSourceIs",
+            COMPLETED_SERVICE_DEPLOYMENT="completedServiceDeployment",
+            CONFIG_FILE="configFile",
+            CONFIGURED_CLASSPATH = "configuredClasspath",
+            CONTEXT_ITEM = "contextItem",
+            CREATED_THREAD="createdThread",
+            DUPLICATE_CLASSPATH="duplicateClasspath",
+            EXCEPTION_THROWN="exceptionThrown",
+            EXCEPTION_WHILE_STOPPING="exceptionWhileStopping",
+            FAILED_CLEAN_SHUTDOWN="failedCleanShutdown",
+            FAILED_DEPLOY_SERVICE="failedDeployService",
+            FAILED_READ_PROPERTIES="failedReadProperties",
+            FOUND_NO_SERVICE_ARCHIVES="foundNoServiceArchives",
+            FOUND_SERVICE_ARCHIVES="foundServiceArchives",
+            ILLEGAL_ARGUMENT_EXCEPTION="illegalArgumentException",
+            ILLEGAL_ACCESS_EXCEPTION="illegalAccessException",
+            INITIALIZATION_EXCEPTION="initializationException",
+            INTIALIZING_EVENT_TABLE="initializingEventTable",
+            INVALID_CLASSPATH_ENTRY="invalidClasspathEntry",
+            INVOCATION_TARGET_EXCEPTION="invocationTargetException",
+            INIT_METHOD_HAS_PARAMETERS="initMethodHasParameters",
+            INIT_METHOD_NOT_VOID="initMethodIsntVoid",
+            INJECT="inject",
+            MISSING_PROPERTY_ENTRY="missingPropertyEntry",
+            MISSING_SPECIAL_VALUE="missingSpecialValue",
+            N_THREADS_LEFT="nThreadsLeft",
+            NO_DEPLOYMENT_DIRECTORY="noDeploymentDirectory",
+            PARENT_CLASS_LOADER_IS="parentClassLoaderIs",
+            POLICY_DECLINED="policyDeclined",
+            PROFILE_CONFIG_EXCEPTION="profileConfigurationException",
+            PROFILE_CONFIG_LOADING="profileConfigLoading",
+            READ_PROPERTIES="readProperties",
+            READ_PROPERTIES_FILE="readPropertiesFile",
+            READING_OBJECT="readingObject",
+            READING_OBJECT_MEMBER_COUNT="readingObject.memberCount",
+            READING_OBJECT_ANNOTATED_MEMBER_FOUND="readingObject.annotatedMemberFound",
+            READING_OBJECT_NON_ANNOTATED_MEMBER_FOUND="readingObject.nonAnnotatedMemberFound",
+            RECEIVED_START="receivedStart",
+            SECURITY_INIT_FAILED="securityInitializationFailed",
+            SECURITY_INIT_SUCCEEDED="securityInitializationSucceeded",
+            SECURITY_INIT_WRONG_POLICY="securityInitializationWrongPolicy",
+            SERVICE_PARENT_CLASSLOADER_IS="serviceParentClassloaderIs",
+            SHOW_COMMAND_LINE_ARGUMENTS="showCommandLineArguments",
+            SHUTDOWN_FAILED="shutdownFailed",
+            SHUTDOWN_METHOD_HAS_PARAMETERS="shutdownMethodHasParameters",
+            SHUTDOWN_METHOD_NOT_VOID="shutdownMethodIsntVoid",
+            STARTER_SERVICE_DEPLOYER_FAILED_INIT="starterServiceDeployerFailedInit",
+            STARTER_SERVICE_DEPLOYER_INITIALIZED="starterServiceDeployerInitialized",
+            STARTER_SERVICE_DEPLOYER_STARTING="starterServiceDeployerStarting",
+            STARTUP_DEPLOYER_FAILED_INIT="startupDeployerFailedInit",
+            STARTUP_DEPLOYER_INITIALIZED="startupDeployerInitialized",
+            STARTUP_DEPLOYER_STARTING="startupDeployerStarting",
+            SYSTEM_CLASSLOADER_IS="systemClassLoaderIs",
+            UNRESOLVED_DEPENDENCY="unresolvedDependency",
+            UNSUPPORTED_ELEMENT="unsupportedElement";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Name.java b/river-container-core/src/main/java/org/apache/river/container/Name.java
new file mode 100644
index 0000000..ed36876
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Name.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Annotation used to flag a field or setter method that should be filled in
+ with the component's name on deployment.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Name {
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Names.java b/river-container-core/src/main/java/org/apache/river/container/Names.java
new file mode 100644
index 0000000..c9c819d
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Names.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.river.container;
+
+/**
+ This class holds the names of various things that are used in the container.
+ * @author trasukg
+ */
+public class Names {
+    public static final String SURROGATE_INSTALLER="surrogateInstaller";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Optional.java b/river-container-core/src/main/java/org/apache/river/container/Optional.java
new file mode 100644
index 0000000..52111da
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Optional.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ <p>
+ Annotation to indicate that the contents of the annotated field should
+ be injected by the AnnotatedClassDeployer when the appropriate reference
+ becomes available.
+ </p>
+
+ <p>
+ By default, the injection is done by type; in other words the value is set
+ to the first thing in the context that is assignable to the target field.
+ If the annotation includes the 'name' attribute, then the injection is done
+ by name; the value is set to whatever is stored under that name in the context.
+ </p>
+
+ <p>
+ If the type of the target happens to be Context, then the context itself
+ will be injected.
+ </p>
+
+ <p>
+ An object in the context will not be injected into any other object until
+ it has been fully resolved and its initialization method has been called.
+ Nonetheless, the target object should not do anything with the injected
+ resource as part of the 'setter' method; it should initialize itself inside
+ a method flagged with the @Init annotation, which will be called when all the
+ @Injected fields/methods have been satisfied.
+ </p>
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Optional {
+    String value() default Strings.EMPTY;
+    InjectionStyle style() default InjectionStyle.DEFAULT;
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ProfileConfigReader.java b/river-container-core/src/main/java/org/apache/river/container/ProfileConfigReader.java
new file mode 100644
index 0000000..fc95b77
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ProfileConfigReader.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.river.container.config.ContainerConfig;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ProfileConfigReader {
+
+    private static final Logger log =
+            Logger.getLogger(ProfileConfigReader.class.getName(), MessageNames.BUNDLE_NAME);
+
+    @Injected
+    private FileUtility fileUtility = null;
+    @Injected(style= InjectionStyle.BY_TYPE)
+    private Context context = null;
+
+    @Injected private String profile=null;
+
+    private ContainerConfig readProfileConfig() throws SAXException, JAXBException, FileNotFoundException, IOException {
+        Unmarshaller um = Bootstrap.createConfigUnmarshaller();
+        FileObject profileDir = fileUtility.getProfileDirectory();
+        FileObject configFile = profileDir.resolveFile(Strings.CONFIG_XML);
+        log.log(Level.FINE, MessageNames.CONFIG_FILE, configFile.toString());
+        InputStream is = configFile.getContent().getInputStream();
+        ContainerConfig containerConfig = (ContainerConfig) um.unmarshal(is);
+        return containerConfig;
+    }
+
+    @Injected
+    private ClassLoader containerClassLoader;
+    
+    @Init
+    public void init() {
+        try {
+            ContainerConfig profileConfig = readProfileConfig();
+            /* We use Object not ClassLoader because it might have been loaded
+             by a different classloader.
+             */
+            
+            log.log(Level.FINE, MessageNames.PROFILE_CONFIG_LOADING, 
+                    new Object[] { containerClassLoader });
+            Bootstrap.processConfiguration(profileConfig, containerClassLoader, context);
+        } catch (Exception ex) {
+            throw new ConfigurationException(ex, MessageNames.PROFILE_CONFIG_EXCEPTION, profile);
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/PropertiesFileReader.java b/river-container-core/src/main/java/org/apache/river/container/PropertiesFileReader.java
new file mode 100644
index 0000000..13dc6e0
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/PropertiesFileReader.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+
+/**
+ *
+ * @author trasukg
+ */
+public class PropertiesFileReader {
+
+    private static final Logger log=
+            Logger.getLogger(PropertiesFileReader.class.getName(),
+            MessageNames.BUNDLE_NAME);
+    
+    @Injected(style=InjectionStyle.BY_TYPE)
+    Context context = null;
+    @Injected
+    FileUtility fileUtility = null;
+
+    @Init
+    public void initialize() {
+        try {
+            FileObject[] childFiles = fileUtility.getProfileDirectory().getChildren();
+            for (FileObject fo: childFiles) {
+                if (fo.getName().getBaseName().endsWith(Strings.DOT_PROPERTIES)
+                        && fo.getType()==FileType.FILE) {
+                    readPropertiesFile(fo);
+                }
+            }
+        } catch (Exception ex) {
+            throw new LocalizedRuntimeException(ex,
+                    MessageNames.BUNDLE_NAME, MessageNames.FAILED_READ_PROPERTIES);
+        }
+    }
+    
+    private void readPropertiesFile(FileObject fo) throws FileSystemException, IOException {
+        String name=fo.getName().getBaseName();
+        Properties props = getProperties(fo);
+        context.put(name, props);
+        log.log(Level.FINE, MessageNames.READ_PROPERTIES_FILE,name);
+        if (log.isLoggable(Level.FINER)) {
+            log.log(Level.FINER, MessageNames.READ_PROPERTIES, 
+                    Utils.format(props));         
+        }
+    }
+
+    public Properties getProperties(FileObject fo) throws FileSystemException, IOException {
+        InputStream is=fo.getContent().getInputStream();
+        Properties props=new Properties();
+        props.load(is);
+        return props;
+    }
+    
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ServiceStarterArchiveFileFilter.java b/river-container-core/src/main/java/org/apache/river/container/ServiceStarterArchiveFileFilter.java
new file mode 100644
index 0000000..be8c826
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ServiceStarterArchiveFileFilter.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.io.File;
+import java.io.FileFilter;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ServiceStarterArchiveFileFilter implements FileFilter {
+
+    public boolean accept(File pathname) {
+        if (!pathname.isFile()) {
+            return false;
+        }
+        if (! pathname.getPath().endsWith(Strings.DOT_SSAR)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/SettableCodebaseClassLoader.java b/river-container-core/src/main/java/org/apache/river/container/SettableCodebaseClassLoader.java
new file mode 100644
index 0000000..38adc49
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/SettableCodebaseClassLoader.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Extends URLClassLoader to allow alteration of the codebase annotation that
+ * will be extracted from the class loader.  Essentially, allows you to use
+ * one source for the actual classpath, and then have a marshalled object
+ * return an arbitrary codebase.
+ * @author trasukg
+ */
+public class SettableCodebaseClassLoader extends URLClassLoader {
+
+    /** Stores the codebase that will be returned as the codebase annotation.
+     *
+     */
+    private URL codebaseURLs[] = new URL[0];
+
+    /**
+     * Construct using a list of urls and the parent classloader.
+     * @param urlList A list of URLs (which may point to directories or
+     * jar files) to form the classpath.
+     * @param extensionClassloader Class loader to use as parent of this
+     * class loader.
+     */
+    SettableCodebaseClassLoader(URL urlList[], ClassLoader extensionClassloader) {
+        super(urlList, extensionClassloader);
+    }
+
+    /**
+     * Get the list of URLs that are used for the codebase annotation.
+     * Note that this list is not the actual classpath (that is in the
+     * superclass).  The codebase URLs are imposed to match whatever the Jini
+     * service wants to expose as its codebase annotation.
+     * @return
+     */
+    @Override
+    public URL[] getURLs() {
+        return codebaseURLs;
+    }
+
+    /** Add a URL to this classpath.
+     */
+    @Override
+    public void addURL(URL url) {
+        URL[] currentURLS = super.getURLs();
+        for (int i = 0; i < currentURLS.length; i++) {
+            if (url.equals(currentURLS[i])) {
+                return;
+            }
+        }
+        super.addURL(url);
+    }
+
+    /**
+     * Set the codebase URLs to an arbitrary list of URLs.  These URLs form the
+     * codebase annotation for classes loaded through this classloader.
+     * For the sake of general paranoia, sets the codebase to a copy of the
+     * provided array.
+     * @param codebase
+     */
+    public void setCodebase(URL[] codebase) {
+        if (codebase == null || codebase.length==0) {
+            codebaseURLs = new URL[]{};
+            return;
+        }
+
+        codebaseURLs = new URL[codebase.length];
+        System.arraycopy(codebase, 0, codebaseURLs, 0, codebase.length);
+
+    }
+
+    static SettableCodebaseClassLoader createLoader(ClassLoader parent,
+            File commonDirectory)
+            throws MalformedURLException, IOException {
+        List urlList = new ArrayList();
+
+        if (!commonDirectory.isDirectory()) {
+            /* There's no common directory, so we'll just use an empty url list
+            to create the classloader.
+             */
+        } else {
+            /* Create based around the unpacked directory.
+             * TODO: Maybe later;  seems to me this would make a decent idea,
+             * but the Surrogate spec specifically disallows the use of the
+             * codebase attribute, and seems to require that all classes be
+             * in the root of the surrogate package jar file.
+
+
+            urlList.add(commonDirectory.toURI().toURL());
+
+            /* Add all jar files in the directory. * /
+            FileFilter filter = new JarFilter();
+            File[] jars = commonDirectory.listFiles(filter);
+            for (int i = 0; i < jars.length; i++) {
+                urlList.add(jars[i].toURI().toURL());
+            }
+            */
+
+        }
+        URL[] urlArray = new URL[urlList.size()];
+        for (int i = 0; i < urlArray.length; i++) {
+            urlArray[i] = (URL) urlList.get(i);
+        }
+        SettableCodebaseClassLoader loader = null;
+
+        loader = new SettableCodebaseClassLoader(urlArray, parent);
+        return loader;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer listString = new StringBuffer();
+        listString.append(getClass().getName() + " [");
+        URL[] urlArray = super.getURLs();
+        for (int i = 0; i < urlArray.length; i++) {
+            listString.append(" ");
+            listString.append(urlArray[i]);
+        }
+        listString.append("], codebase [");
+        urlArray = getURLs();
+        for (int i = 0; i < urlArray.length; i++) {
+            listString.append(" ");
+            listString.append(urlArray[i]);
+        }
+        listString.append("]");
+        return listString.toString();
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ShowContextToConsole.java b/river-container-core/src/main/java/org/apache/river/container/ShowContextToConsole.java
new file mode 100644
index 0000000..85a5e42
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ShowContextToConsole.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ShowContextToConsole implements ContextListener {
+
+    private static final Logger log=Logger.getLogger(ShowContextToConsole.class.getName(), MessageNames.BUNDLE_NAME);
+
+    private Context context;
+
+    public void init() {
+
+        for(String key: context.contents.keySet()) {
+            log.log(Level.FINE,MessageNames.CONTEXT_ITEM ,
+                    new Object[] {key, context.contents.get(key)});
+        }
+    }
+
+    public void put(String name, Object o) {
+
+    }
+
+    public void remove(Object o) {
+
+    }
+
+    public void setContext(Context ctx) {
+        context=ctx;
+    }
+
+    public void initComplete() {
+        init();
+    }
+
+    @Override
+    public void shutDown() {
+        
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Shutdown.java b/river-container-core/src/main/java/org/apache/river/container/Shutdown.java
new file mode 100644
index 0000000..62be5b0
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Shutdown.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Identifies a method which should be called when the context is being shut down.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Shutdown {
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ShutdownListener.java b/river-container-core/src/main/java/org/apache/river/container/ShutdownListener.java
new file mode 100644
index 0000000..5f3f529
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ShutdownListener.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ShutdownListener implements ShutdownListenerMXBean, Runnable {
+    
+    @Injected(style= InjectionStyle.BY_TYPE)
+    private Context context=null;
+    
+    public void run() {
+        synchronized(this) {
+            try {
+                wait();
+                context.shutDown();
+                System.exit(0);
+            } catch (InterruptedException ex) {
+                Logger.getLogger(ShutdownListener.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+    }
+
+    public void shutdown() {
+        synchronized(this) {
+            this.notifyAll();
+        }
+    }
+
+    @Init
+    public void init() {
+        new Thread(this).start();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/ShutdownListenerMXBean.java b/river-container-core/src/main/java/org/apache/river/container/ShutdownListenerMXBean.java
new file mode 100644
index 0000000..8b2ca01
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/ShutdownListenerMXBean.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import javax.management.MXBean;
+
+/**
+ *
+ * @author trasukg
+ */
+@MXBean
+public interface ShutdownListenerMXBean {
+    public void shutdown();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Strings.java b/river-container-core/src/main/java/org/apache/river/container/Strings.java
new file mode 100644
index 0000000..0e7a9a0
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Strings.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+/**
+ *
+ * @author trasukg
+ */
+public class Strings {
+
+    public static final String
+            ADD_URL="addURL",
+            BOOTSTRAP_CLASS_LOADER="bootstrapClassLoader",
+            CLASS_LOADERS="classLoaders",
+            COMMAND_LINE_ARGS = "commandLineArguments",
+            CONFIG_XML = "config.xml",
+            CORE_CONFIG_XML="core-config.xml",
+            CONTAINER_CLASS_LOADER="containerClassLoader",
+            CONTAINER_JMX_DOMAIN="org.apache.river.container",
+            CONTEXT_CLASS = "org.apache.river.container.Context",
+            DASH = "-",
+            DEFAULT = "default",
+            DEFAULT_DEPLOY_DIRECTORY="deploy",
+            DEFAULT_DISCOVERY_CONTEXT = "defaultDiscoveryContext",
+            DOLLAR="$",
+            DOT=".",
+            DOT_CLASS=".class",
+            DOT_JAR=".jar",
+            DOT_PROPERTIES=".properties",
+            DOT_SSAR=".ssar",
+            EMPTY = "",
+            GET_ADMIN="getAdmin",
+            FILE_UTILITY="fileUtility",
+            INIT_COMPLETE="initComplete",
+            JAR="jar",
+            LIB="lib",
+            LIB_DL="lib-dl",
+            LIFECYCLE_CLASS="com.sun.jini.start.LifeCycle",
+            MAIN="main",
+            NAME="name",
+            PLATFORM_JARS="platformJars",
+            PLATFORM_CODEBASE="platformCodebase",
+            PROFILE = "profile",
+            PROFILE_DIR="profileDirectory",
+            PUT = "put",
+            PUT_SPECIAL_ENTRY="putSpecialEntry",
+            READ="read",
+            SECURITY_POLICY="securityPolicy",
+            SPACE=" ",
+            SSAR="ssar",
+            SET = "set",
+            SET_WORKING_DIRECTORY="setWorkingDirectory",
+            SLASH="/",
+            START_CLASS="startClass",
+            START_PARAMETERS="startParameters",
+            START_PROPERTIES="start.properties",
+            STARTER_SERVICE_DEPLOYER_CONFIG="service-starter.cfg",
+            SYSTEM_CLASS_LOADER="systemClassLoader",
+            TYPE="type",
+            UNKNOWN="unknown",
+            UNNAMED="unnamed",
+            WHITESPACE_SEPARATORS=" \t\n\r",
+            WORK="work";
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/SurrogateInstaller.java b/river-container-core/src/main/java/org/apache/river/container/SurrogateInstaller.java
new file mode 100644
index 0000000..e180104
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/SurrogateInstaller.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import org.apache.river.container.deployer.ApplicationEnvironment;
+import java.io.File;
+
+/**
+ *
+ * @author trasukg
+ */
+public class SurrogateInstaller {
+
+    /**
+    Create and install a surrogate based on a surrogate file which has
+    been unpacked into a working directory.
+    
+    <p>This seems to be a lot like installing a generic application, apart
+    from the specific ways of determining the surrogate's class, so
+    one wonders whether we might eventually make this a plain application
+    loader, and separate out the surrogate-specific items into the
+    connector.
+    </p>
+    <p>More formally then, the surrogate connector could read a surrogate
+    file, then create a plain application that represents the surrogate.
+    </p>
+    <p>In that case, the surrogate application is simply a plain application
+    that has some extra limitations placed on it (e.g. no access to
+    local resources, a more restrictive security policy, etc).
+    </p>
+    
+    @param workingDir
+     */
+    void installSurrogate(Host host, File workingDir) {
+        // Create a context for the surrogate.
+        ApplicationEnvironment appEnv = host.newApplicationEnvironment();
+
+        try {
+            /* Configure the application environment. */
+            configureApplicationEnvironment(appEnv, workingDir);
+
+            /* Startup the application environment. */
+            // Initialize the class loader with the surrogate's classes
+            /* TODO: Set the parent classloader to what? 
+            SettableCodebaseClassLoader classLoader =
+            SettableCodebaseClassLoader.createLoader(null, workingDir);
+            appEnv.setClassLoader(classLoader);
+             */
+            // Instantiate the surrogate.
+            // Try the surrogate's getCodebase method to find the codebase
+            /*
+            If codebase method returns nothing,
+            read the manifest to get the
+            codebase entries.
+             */
+            /*
+             * Setup the discovery manager for the surrogate.
+             */
+            /*
+             * Publish the surrogate's codebase jars through hosts's codebase server.
+             */
+            /* Initialize the surrogate.
+             */
+            /*
+             * In case of failure, unpublish the codebase and clean up.
+             */
+            /*
+             * Initiate liveness callbacks.
+             */
+        } catch (Exception e) {
+            /* TODO: Handle this properly! */
+            e.printStackTrace();
+        }
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    private void configureApplicationEnvironment(ApplicationEnvironment appEnv, File workingDir) {
+        /* Read the manifest to get the surrogate's class. */
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/SystemClassloaderInitializer.java b/river-container-core/src/main/java/org/apache/river/container/SystemClassloaderInitializer.java
new file mode 100644
index 0000000..6654f7b
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/SystemClassloaderInitializer.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container;
+
+import java.net.URLClassLoader;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+    This class simply sets the systemClassLoader property in the context.
+ * @author trasukg
+ */
+public class SystemClassloaderInitializer {
+    private static final Logger log=Logger.getLogger(SystemClassloaderInitializer.class.getName(), MessageNames.BUNDLE_NAME);
+    
+    @Injected(style= InjectionStyle.BY_TYPE) private Context context=null;
+    
+    @Init
+    public void init() {
+        Map<String, ClassLoader> classLoaders=
+                (Map<String, ClassLoader>) context.get(Strings.CLASS_LOADERS);
+        ClassLoader cl=classLoaders.get(Strings.SYSTEM_CLASS_LOADER);
+        for (String id: classLoaders.keySet()) {
+            context.put(id, classLoaders.get(id));
+        }
+        String classpath=Strings.UNKNOWN;
+        if(cl instanceof URLClassLoader) {
+            URLClassLoader ucl=(URLClassLoader) cl;
+            classpath=Utils.format(ucl.getURLs());
+        }
+        log.log(Level.FINE, MessageNames.SYSTEM_CLASSLOADER_IS,
+                new Object[] {
+                    cl.toString(), classpath
+                });
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/Utils.java b/river-container-core/src/main/java/org/apache/river/container/Utils.java
new file mode 100644
index 0000000..7da2d6f
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/Utils.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+
+/**
+
+ @author trasukg
+ */
+public class Utils {
+
+    public static String format(Object array[]) {
+        if (array == null) {
+            return "null";
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (int j = 0; j < array.length; j++) {
+            if (j != 0) {
+                sb.append(", ");
+            }
+            sb.append("'");
+            sb.append(array[j].toString());
+            sb.append("'");
+        }
+        sb.append("]");
+
+        return sb.toString();
+    }
+
+    public static String format(Properties props) {
+        if (props == null) {
+            return "null";
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (Map.Entry entry : props.entrySet()) {
+            boolean first = true;
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
+            }
+            sb.append(entry.getKey() + "=\"");
+            sb.append(entry.getValue());
+            sb.append("\"");
+        }
+        sb.append("]");
+
+        return sb.toString();
+    }
+
+    public static String[] splitOnWhitespace(String input) {
+        List<String> strings = new ArrayList<String>();
+        StringTokenizer tok = new StringTokenizer(Strings.WHITESPACE_SEPARATORS);
+        while (tok.hasMoreTokens()) {
+            strings.add(tok.nextToken());
+        }
+        return (String[]) strings.toArray(new String[0]);
+    }
+
+    public static List<FileObject> findChildrenWithSuffix(FileObject dir, String suffix) throws FileSystemException {
+
+        List<FileObject> ret = new ArrayList<FileObject>();
+
+        for (FileObject fo : dir.getChildren()) {
+            if (fo.getType() == FileType.FILE && fo.getName().getBaseName().endsWith(suffix)) {
+                ret.add(fo);
+            }
+        }
+        return ret;
+    }
+
+    public static void logClassLoaderHierarchy(Logger log,
+            Class cls) {
+        logClassLoaderHierarchy(log, Level.FINE, cls);
+    }
+
+    public static void logClassLoaderHierarchy(Logger log, Level level,
+            Class cls) {
+        log.log(level, MessageNames.CLASSLOADER_IS,
+                new Object[]{cls.getName(), cls.getClassLoader()});
+        ClassLoader parent = cls.getClassLoader().getParent();
+        while (parent != null) {
+            log.log(level, MessageNames.PARENT_CLASS_LOADER_IS,
+                    new Object[]{parent});
+            parent = parent.getParent();
+        }
+    }
+
+    public static void logClassLoaderHierarchy(Logger log,
+            Level level,
+            ClassLoader loader) {
+        log.log(level, MessageNames.CLASSLOADER_IS,
+                new Object[]{null, loader});
+        ClassLoader parent = loader.getParent();
+        while (parent != null) {
+            log.log(level, MessageNames.PARENT_CLASS_LOADER_IS,
+                    new Object[]{parent});
+            parent = parent.getParent();
+        }
+    }
+    
+    public static String stackTrace(Throwable t) {
+        StringWriter s=new StringWriter();
+        PrintWriter pw=new PrintWriter(s);
+        t.printStackTrace(pw);
+        return s.toString();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/ASTNode.java b/river-container-core/src/main/java/org/apache/river/container/classloading/ASTNode.java
new file mode 100644
index 0000000..f9e9a82
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/ASTNode.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.classloading;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ASTNode extends SimpleNode {
+
+    private Object value=null;
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    
+    public ASTNode(int i) {
+        super(i);
+    }
+
+    public ASTNode(ClasspathExpressionParser p, int i) {
+        super(p, i);
+    }
+
+    public String getName() {
+        return ClasspathExpressionParserTreeConstants.jjtNodeName[id];
+    }
+
+    public String toString() {
+        if (id==ClasspathExpressionParserTreeConstants.JJTSYMBOL
+                || id==ClasspathExpressionParserTreeConstants.JJTSTRINGLITERAL) {
+            return getValue().toString();
+        }
+        String childList = childList();
+        if (!Strings.EMPTY.equals(childList)) {
+            return getName() + " " + childList;
+        } else {
+            return getName();
+        }
+    }
+
+    public String childList() {
+        StringBuffer sb = new StringBuffer();
+        if (jjtGetNumChildren() != 0) {
+            boolean first = true;
+            for (int i = 0; i < jjtGetNumChildren(); i++) {
+                if (!first) {
+                    sb.append(" ");
+                } else {
+                    first = false;
+                }
+                String childStr = jjtGetChild(i).toString();
+                if (childStr.indexOf(Strings.SPACE) != -1) {
+                    sb.append(Strings.LPAREN);
+                    sb.append(childStr);
+                    sb.append(Strings.RPAREN);
+                } else {
+                    sb.append(jjtGetChild(i).toString());
+                }
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/Acceptor.java b/river-container-core/src/main/java/org/apache/river/container/classloading/Acceptor.java
new file mode 100644
index 0000000..69989fc
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/Acceptor.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.classloading;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface Acceptor {
+    public boolean acceptsResource(String resourcePath);
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/AllAcceptor.java b/river-container-core/src/main/java/org/apache/river/container/classloading/AllAcceptor.java
new file mode 100644
index 0000000..ec60c5b
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/AllAcceptor.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.classloading;
+
+/**
+ *
+ * @author trasukg
+ */
+public class AllAcceptor implements Acceptor {
+
+    public boolean acceptsClass(String className) {
+        return true;
+    }
+
+    public boolean acceptsResource(String resourcePath) {
+        return true;
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathEntry.java b/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathEntry.java
new file mode 100644
index 0000000..f0024a0
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathEntry.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.classloading;
+
+import java.util.logging.Logger;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.river.container.MessageNames;
+
+/**
+ * A ClassPathEntry is used by the VirtualFileSystemClassLoader, and is a
+ combination of a ClasspathFilter and the fileObject that points to the entry's
+ jar file.  It effectively represents an entry like 'container.jar(org.apache.ABC)',
+ which would mean 'the class org.apache.ABC contained inside the jar file
+ container.jar'.  The idea is to include selected packages from a jar file on the
+ classpath,
+ * @author trasukg
+ */
+public class ClasspathEntry {
+    private ClasspathFilter classpathFilter=null;
+
+    private FileObject fileObject=null;
+
+    public ClasspathEntry(ClasspathFilter filter, FileObject fileObject) {
+        this.fileObject=fileObject;
+        this.classpathFilter=filter;
+    }
+
+    public FileObject resolveFile(String name) throws FileSystemException {
+        if ((classpathFilter.acceptsResource(name))) {
+            return fileObject.resolveFile(name);
+        }
+        return null;
+    }
+    @Override
+    public String toString() {
+        return fileObject.toString() + classpathFilter.toString();
+    }
+        
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathFilter.java b/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathFilter.java
new file mode 100644
index 0000000..2748674
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathFilter.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.classloading;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ClasspathFilter {
+
+    private String jarName = null;
+
+    public void setJarName(String s) {
+        jarName = s;
+    }
+
+    public String getJarName() {
+        return jarName;
+    }
+    List<Acceptor> acceptors = new ArrayList<Acceptor>();
+
+    public List<Acceptor> getAcceptors() {
+        return acceptors;
+    }
+
+    /**
+    Returns true if this filter accepts the given resource path.
+    Resource path is in the form '/META-INF/ABC.xyz'
+    @param resourcePath
+    @return
+     */
+    public boolean acceptsResource(String resourcePath) {
+
+        for (Acceptor a : getAcceptors()) {
+            if (a.acceptsResource(resourcePath)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    public String toString() {
+        return "("+acceptorString()+")";
+    }
+    
+    private String acceptorString() {
+        StringBuilder sb=new StringBuilder();
+        boolean first=true;
+        for (Acceptor a: getAcceptors()) {
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first=false;
+            }
+            sb.append(a);     
+        }
+        return sb.toString();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathFilterBuilder.java b/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathFilterBuilder.java
new file mode 100644
index 0000000..fc2b1ce
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/ClasspathFilterBuilder.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.classloading;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+import org.apache.river.container.LocalizedRuntimeException;
+import org.apache.river.container.MessageNames;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ClasspathFilterBuilder {
+
+    private static final Logger log = Logger.getLogger(ClasspathFilterBuilder.class.getName());
+
+    public List<ClasspathFilter> parseToFilters(String input) {
+        try {
+            ASTcpExpression expression = classpathExpressionFromString(input);
+            List<ClasspathFilter> filters = filtersFromClasspathExpression(expression);
+            return filters;
+        } catch (ParseException ex) {
+            throw new LocalizedRuntimeException(MessageNames.BUNDLE_NAME,
+                    MessageNames.BAD_CLASSPATH_EXPR,
+                    new Object[]{input, ex.getMessage()});
+        }
+    }
+
+    private ASTcpExpression classpathExpressionFromString(String input) throws ParseException {
+        Reader r = new StringReader(input);
+        ClasspathExpressionParser parser = new ClasspathExpressionParser(r);
+        parser.cpExpression();
+        ASTcpExpression expression = (ASTcpExpression) parser.jjtree.popNode();
+        return expression;
+    }
+
+    private List<ClasspathFilter> filtersFromClasspathExpression(ASTcpExpression expression) {
+        List<ClasspathFilter> filters = new ArrayList<ClasspathFilter>();
+        for (int i = 0; i < expression.jjtGetNumChildren(); i++) {
+            ASTcpClause clause = (ASTcpClause) expression.jjtGetChild(i);
+            ClasspathFilter cpf = makeFilter(clause);
+            filters.add(cpf);
+        }
+        return filters;
+    }
+
+    public ClasspathFilter makeFilter(ASTcpClause expression) {
+        /* First node is the jar name.  Subsequent nodes are the filter
+        conditions.
+         */
+        ClasspathFilter cpf = new ClasspathFilter();
+        cpf.setJarName(expression.jjtGetChild(0).toString());
+        for (int i = 1; i < expression.jjtGetNumChildren(); i++) {
+            Node node = expression.jjtGetChild(i);
+            if (node instanceof ASTsymbol) {
+                String resourceName = VirtualFileSystemClassLoader.classToResourceName(node.toString());
+                log.fine("Building ResourceAcceptor with string '" + resourceName + "'");
+                Acceptor acc = new ResourceAcceptor(resourceName);
+                cpf.getAcceptors().add(acc);
+            }
+            if (node instanceof ASTstringLiteral) {
+                log.fine("Building ResourceAcceptor with string '" + node.toString() + "'");
+                Acceptor acc = new ResourceAcceptor(node.toString());
+                cpf.getAcceptors().add(acc);
+            }
+        }
+        /* If there were no filter clauses, hence no acceptors, allow all
+        patterns.
+         */
+        if (cpf.getAcceptors().isEmpty()) {
+            cpf.getAcceptors().add(new AllAcceptor());
+        }
+        return cpf;
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/ResourceAcceptor.java b/river-container-core/src/main/java/org/apache/river/container/classloading/ResourceAcceptor.java
new file mode 100644
index 0000000..d848370
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/ResourceAcceptor.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.classloading;
+
+import java.util.Arrays;
+import org.apache.river.container.Utils;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ResourceAcceptor implements Acceptor {
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ResourceAcceptor other = (ResourceAcceptor) obj;
+        if (!Arrays.deepEquals(this.pathSteps, other.pathSteps)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 37 * hash + Arrays.deepHashCode(this.pathSteps);
+        return hash;
+    }
+
+    String[] pathSteps=null;
+
+    public ResourceAcceptor(String resourcePath) {
+        pathSteps=resourcePath.split(Strings.SLASH);
+    }
+
+    public boolean acceptsResource(String resourcePath) {
+        /* A better programmer would use regular expressions here.
+         But then he would have two problems...
+         */
+        String[] inputPathSteps=resourcePath.split(Strings.SLASH);
+        int inputIndex=0, pathStepIndex=0;
+        for (;;) {
+            /* Hit the end of both paths at the same time. */
+            if (inputIndex==inputPathSteps.length && pathStepIndex==pathSteps.length) {
+                return true;
+            }
+            /* End of one path but not the other. */
+            if (inputIndex==inputPathSteps.length || pathStepIndex==pathSteps.length) {
+                return false;
+            }
+            if (pathSteps[pathStepIndex].equals(inputPathSteps[inputIndex])) {
+                pathStepIndex++;
+                inputIndex++;
+                continue;
+            }
+            if (Strings.STAR.equals(pathSteps[pathStepIndex])) {
+                pathStepIndex++;
+                inputIndex++;
+                continue;
+            }
+            else {
+                return false;
+            }
+        }
+    }
+
+    public String toString() {
+        return "ResourceAcceptor(" + Utils.format(pathSteps) + ")";
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/Strings.java b/river-container-core/src/main/java/org/apache/river/container/classloading/Strings.java
new file mode 100644
index 0000000..4b42406
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/Strings.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.classloading;
+
+/**
+ *
+ * @author trasukg
+ */
+public class Strings {
+    public static final String
+            DOT=".",
+            DOT_CLASS=".class",
+            EMPTY="",
+            GET_NAME="getName",
+            GET_VALUE="getValue",
+            ID="id",
+            JAR="jar",
+            JJT_GET_CHILD="jjtGetChild",
+            JJT_GET_NUM_CHILDREN="jjtGetNumChildren",
+            JJTLITERAL="JJTLITERAL",
+            JJTSYMBOL="JJTSYMBOL",
+            LIB="lib",
+            LPAREN="(",
+            RPAREN=")",
+            SLASH="/",
+            SPACE=" ",
+            STAR="*";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/classloading/VirtualFileSystemClassLoader.java b/river-container-core/src/main/java/org/apache/river/container/classloading/VirtualFileSystemClassLoader.java
new file mode 100644
index 0000000..19a6c63
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/classloading/VirtualFileSystemClassLoader.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.classloading;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.security.Security;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileUtil;
+import org.apache.river.container.LocalizedRuntimeException;
+import org.apache.river.container.MessageNames;
+
+/**
+
+ @author trasukg
+ */
+public class VirtualFileSystemClassLoader extends URLClassLoader {
+
+    private FileObject fileSystemRoot = null;
+    private List<ClasspathEntry> classpathEntries = new ArrayList<ClasspathEntry>();
+    private CodeSource codeSource = null;
+
+    public VirtualFileSystemClassLoader(FileObject fileSystemRoot, ClassLoader parent, CodeSource codeSource) {
+        super(new URL[0], parent);
+        this.fileSystemRoot = fileSystemRoot;
+        this.codeSource = codeSource;
+    }
+
+    public static String classToResourceName(String name) {
+        String resourceName = name.replace(Strings.DOT, Strings.SLASH).concat(Strings.DOT_CLASS);
+        return resourceName;
+    }
+
+    /**
+     Add the given classpath to this classloader, based on the default root
+     supplied at construction time.
+
+     @param classPath
+     */
+    public void addClassPathEntry(String classPath) {
+
+        addClassPathEntry(fileSystemRoot, classPath);
+    }
+
+    /**
+     Add the given classpath to this classloader, based on the given fileRoot.
+     The classpath can contain multiple entries, separated by colons, e.g.
+     'jsk-platform.jar:jsk-lib.jar'.<br> Each entry can either be a jar file or
+     a jar file with a list of classes that the jar file can be used to provide.
+     For instance, 'surrogate.jar(org.apache.ABC, org.apache.DEF)'.
+
+     @param fileRoot
+     @param classPath
+     */
+    public void addClassPathEntry(FileObject fileRoot, String classPath) {
+
+        try {
+            /*
+             Classpath entry is a jar file with filter expressions that can be
+             understood by ClasspathFilterBuilder.
+             */
+            /*
+             Create a nested file system from it and add it to the file objects.
+             */
+            List<ClasspathFilter> filters = new ClasspathFilterBuilder().parseToFilters(classPath);
+            addClasspathFilters(filters, fileRoot);
+        } catch (FileSystemException ex) {
+            throw new LocalizedRuntimeException(ex, MessageNames.BUNDLE_NAME, MessageNames.INVALID_CLASSPATH_ENTRY, classPath);
+        }
+    }
+
+    public void addClasspathFilters(List<ClasspathFilter> filters, FileObject fileRoot) throws FileSystemException {
+        for (ClasspathFilter filter : filters) {
+            FileObject entryObject = fileRoot.resolveFile(filter.getJarName());
+
+            FileObject entryFileSystem =
+                    fileRoot.getFileSystem().getFileSystemManager().createFileSystem(entryObject);
+            classpathEntries.add(new ClasspathEntry(filter, entryFileSystem));
+        }
+    }
+
+    /**
+     Find a resource by searching through all the classpath entries that have
+     been set up.
+
+     @param name
+     @return
+     */
+    @Override
+    public URL findResource(final String name) {
+        try {
+            return (URL) Security.doPrivileged(new PrivilegedExceptionAction<URL>() {
+
+                @Override
+                public URL run() throws Exception {
+                    FileObject fo = findResourceFileObject(name);
+                    return fo == null ? null : fo.getURL();
+                }
+            });
+
+        } catch (Exception ex) {
+            Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
+        }
+        return null;
+    }
+
+    @Override
+    public Enumeration<URL> findResources(final String name) throws IOException {
+
+        Enumeration result = (Enumeration)
+                Security.doPrivileged(new PrivilegedAction<Enumeration>() {
+
+            public Enumeration run() {
+                List<URL> urlList = new ArrayList<URL>();
+                try {
+
+                    List<FileObject> foList = findResourceFileObjects(name);
+                    for (FileObject fo : foList) {
+                        urlList.add(fo.getURL());
+                    }
+                } catch (FileSystemException ex) {
+                    Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
+                }
+                return Collections.enumeration(urlList);
+            }
+        });
+        return result;
+    }
+
+    /**
+     Find the file object for a resource by searching through all the classpath
+     entries that have been set up.
+
+     @param name
+     @return
+     */
+    public FileObject findResourceFileObject(String name) {
+        for (ClasspathEntry cpEntry : classpathEntries) {
+            try {
+                FileObject fo = cpEntry.resolveFile(name);
+                if (fo != null && fo.isReadable()) {
+                    return fo;
+                }
+            } catch (FileSystemException ex) {
+                Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+        return null;
+    }
+
+    /**
+     Find the all the file objects for a resource by searching through all the
+     classpath entries that have been set up.
+
+     @param name
+     @return
+     */
+    public List<FileObject> findResourceFileObjects(String name) {
+        List<FileObject> foList = new ArrayList<FileObject>();
+        for (ClasspathEntry cpEntry : classpathEntries) {
+            try {
+                FileObject fo = cpEntry.resolveFile(name);
+                if (fo != null && fo.isReadable()) {
+                    foList.add(fo);
+                }
+            } catch (FileSystemException ex) {
+                Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+        return foList;
+    }
+
+    @Override
+    protected Class<?> findClass(final String name) throws ClassNotFoundException {
+        try {
+            return (Class) Security.doPrivileged(new PrivilegedExceptionAction<Class>() {
+
+                public Class run() throws ClassNotFoundException {
+                    String resourceName = classToResourceName(name);
+                    FileObject resourceFileObject = findResourceFileObject(resourceName);
+                    if (resourceFileObject == null) {
+                        throw new ClassNotFoundException(name + "(" + resourceName + ")");
+                    }
+                    try {
+                        byte[] bytes = FileUtil.getContent(resourceFileObject);
+                        return defineClass(name, bytes, 0, bytes.length);
+                    } catch (IOException ioe) {
+                        throw new ClassNotFoundException(name, ioe);
+                    }
+
+                }
+            });
+        } catch (PrivilegedActionException ex) {
+            throw (ClassNotFoundException) ex.getException();
+        }
+    }
+
+    /**
+     Set the codebase URLs to an arbitrary list of URLs. These URLs form the
+     codebase annotation for classes loaded through this classloader. For the
+     sake of general paranoia, sets the codebase to a copy of the provided
+     array.
+
+     @param codebase
+     */
+    public void setCodebase(URL[] codebase) {
+        if (codebase == null || codebase.length == 0) {
+            codebaseURLs = new URL[]{};
+            return;
+        }
+
+        codebaseURLs = new URL[codebase.length];
+        System.arraycopy(codebase, 0, codebaseURLs, 0, codebase.length);
+
+    }
+
+    /**
+     Get the list of URLs that are used for the codebase annotation. Note that
+     this list is not the actual classpath (that is in the superclass). The
+     codebase URLs are imposed to match whatever the Jini service wants to
+     expose as its codebase annotation.
+
+     @return
+     */
+    @Override
+    public URL[] getURLs() {
+        return codebaseURLs;
+    }
+    /**
+     Stores the codebase that will be returned as the codebase annotation.
+
+     */
+    private URL codebaseURLs[] = new URL[0];
+
+    @Override
+    public String toString() {
+        StringBuffer listString = new StringBuffer();
+        listString.append(format(classpathEntries));
+
+        listString.append(", codebase [");
+        URL[] urlArray = getURLs();
+        for (int i = 0; i < urlArray.length; i++) {
+            listString.append(" ");
+            listString.append(urlArray[i]);
+        }
+        listString.append("]");
+        return listString.toString();
+    }
+
+    public static String format(List<ClasspathEntry> items) {
+        if (items == null) {
+            return "null";
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        boolean first = true;
+        for (Object o : items) {
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
+            }
+            sb.append("'");
+            sb.append(o.toString());
+            sb.append("'");
+        }
+        sb.append("]");
+
+        return sb.toString();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/ClassServer.java b/river-container-core/src/main/java/org/apache/river/container/codebase/ClassServer.java
new file mode 100644
index 0000000..c99d1ea
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/ClassServer.java
@@ -0,0 +1,464 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+
+ Originally taken from com.sun.jini.tool.ClassServer, then refactored to
+ plug into the surrogate container.
+ */
+package org.apache.river.container.codebase;
+
+import com.sun.jini.logging.Levels;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.river.container.Init;
+import org.apache.river.container.Injected;
+import org.apache.river.container.InjectionStyle;
+import org.apache.river.container.LocalizedRuntimeException;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.Shutdown;
+import org.apache.river.container.work.WorkManager;
+
+/**
+ * A simple HTTP server, for serving up JAR and class files. <p> The following
+ * items are discussed below: <ul> <li>{@linkplain #main Command line options}
+ * <li><a href="#logging">Logging</a> <li><a href="#running">Examples for
+ * running ClassServer</a> </ul> <p> <a name="logging"><h3>Logging</h3></a> <p>
+ *
+ * This implementation uses the {@link Logger} named
+ * <code>com.sun.jini.tool.ClassServer</code> to log information at the
+ * following logging levels: <p> <table border="1" cellpadding="5"
+ * summary="Describes logging performed by ClassServer at different logging
+ * levels"> <caption halign="center" valign="top"><b><code>
+ *    com.sun.jini.tool.ClassServer</code></b></caption>
+ *
+ * <tr> <th scope="col">Level</th> <th scope="col">Description</th> </tr> <tr> <td>{@link Level#SEVERE SEVERE}</td>
+ * <td>failure to accept an incoming connection</td> </tr> <tr> <td>{@link Level#WARNING WARNING}</td>
+ * <td>failure to read the contents of a requested file, failure to find the
+ * message resource bundle, failure while executing the
+ * <code>-stop</code> option </td> </tr> <tr> <td>{@link Level#INFO INFO}</td>
+ * <td>server startup and termination</td> </tr> <tr> <td>{@link Level#CONFIG CONFIG}</td>
+ * <td>the JAR files being used for
+ * <code>-trees</code></td> </tr> <tr> <td>{@link Levels#HANDLED HANDLED}</td>
+ * <td>failure reading an HTTP request or writing a response</td> </tr> <tr> <td>{@link Level#FINE FINE}</td>
+ * <td>bad HTTP requests, HTTP requests for nonexistent files</td> </tr> <tr> <td>{@link Level#FINER FINER}</td>
+ * <td>good HTTP requests</td> </tr> </table>
+ *
+ *
+ */
+public class ClassServer implements CodebaseHandler {
+
+    private static final Logger logger =
+            Logger.getLogger(ClassServer.class.getName(), MessageNames.BUNDLE_NAME);
+    /**
+     * Server socket to accept connections on
+     */
+    private ServerSocket server;
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private WorkManager workManager = null;
+    @Injected(Strings.CLASS_SERVER_PROPERTIES)
+    private Properties properties;
+    Map<String, ClassServerCodebaseContext> contexts =
+            new HashMap<String, ClassServerCodebaseContext>();
+
+    @Init
+    public void init() {
+        try {
+            establishServerSocket();
+            workManager.queueTask(Thread.currentThread().getContextClassLoader(),
+                    new Runnable() {
+
+                        @Override
+                        public void run() {
+                            ClassServer.this.run();
+                        }
+                    });
+        } catch (IOException ex) {
+            logger.log(Level.SEVERE, MessageNames.CLASS_SERVER_INIT_FAILED, ex);
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void establishServerSocket() throws IOException, SocketException {
+        server = new ServerSocket();
+        server.setReuseAddress(true);
+        String initialPortStr = properties.getProperty(Strings.INITIAL_PORT);
+        if (initialPortStr == null) {
+            throw new LocalizedRuntimeException(
+                    MessageNames.BUNDLE_NAME,
+                    MessageNames.MISSING_PROPERTY_ENTRY,
+                    new Object[]{
+                        Strings.CLASS_SERVER_PROPERTIES,
+                        Strings.INITIAL_PORT
+                    });
+        }
+        int initialPort = Integer.parseInt(initialPortStr);
+        try {
+            server.bind(new InetSocketAddress(initialPort));
+            logger.log(Level.INFO, MessageNames.CLASS_SERVER_ESTABLISHED,
+                    new Object[]{server.getLocalSocketAddress(),
+                        server.getLocalPort()});
+        } catch (BindException be) {
+            IOException ioe = new IOException("failure to bind to port: " + initialPort, be);
+            throw ioe;
+        }
+    }
+
+    /**
+     * Just keep looping, spawning a new thread for each incoming request. It's
+     * tempting here to have the last operation queue another accept() task
+     * rather than setup a loop. Wonder what the ramifications would be? We'd
+     * have more opportunities to end the service task, but possibly more
+     * development effort.
+     */
+    public void run() {
+        try {
+            while (true) {
+                final Socket connectedSocket = server.accept();
+                /*
+                 * Boy, would this be a nice spot to have closures!
+                 */
+                workManager.queueTask(
+                        Thread.currentThread().getContextClassLoader(),
+                        new Runnable() {
+
+                            @Override
+                            public void run() {
+                                processRequest(connectedSocket);
+                            }
+                        });
+            }
+        } catch (IOException e) {
+            synchronized (this) {
+                if (!server.isClosed()) {
+                    logger.log(Level.SEVERE, MessageNames.CLASS_SERVER_ERROR_ACCEPTING_CONNECTIONS, e);
+                }
+                terminate();
+            }
+        }
+    }
+
+    /**
+     * Close the server socket, causing the thread to terminate.
+     */
+    @Shutdown
+    public synchronized void terminate() {
+        try {
+            server.close();
+        } catch (IOException e) {
+            logger.log(Level.FINE, MessageNames.CLASS_SERVER_EXCEPTION_DURING_SHUTDOWN, e);
+        }
+        logger.log(Level.INFO, MessageNames.CLASS_SERVER_TERMINATED,
+                new Object[]{server.getLocalSocketAddress(),
+                    server.getLocalPort()});
+    }
+
+    /**
+     * Returns the port on which this server is listening.
+     */
+    public int getPort() {
+        return server.getLocalPort();
+    }
+
+    /**
+     * Returns the hostname that the server is listening to.
+     *
+     * @return
+     */
+    public String getHost() throws UnknownHostException {
+        return InetAddress.getLocalHost().getCanonicalHostName();
+    }
+
+    /**
+     * Read up to CRLF, return false if EOF
+     */
+    private static boolean readLine(InputStream in, StringBuffer buf)
+            throws IOException {
+        while (true) {
+            int c = in.read();
+            if (c < 0) {
+                return buf.length() > 0;
+            }
+            /*
+             * The characters below are part of the http protocol and not
+             * localizable, so we're OK with character literals.
+             */
+            if (c == '\r') {
+                in.mark(1);
+                c = in.read();
+                if (c != '\n') {
+                    in.reset();
+                }
+                return true;
+            }
+            if (c == '\n') {
+                return true;
+            }
+            buf.append((char) c);
+        }
+    }
+
+    /**
+     * Parse % HEX HEX from s starting at i
+     */
+    private static char decode(String s, int i) {
+        return (char) Integer.parseInt(s.substring(i + 1, i + 3), 16);
+    }
+
+    /**
+     * Decode escape sequences
+     */
+    private static String decode(String path) {
+        try {
+            for (int i = path.indexOf('%');
+                    i >= 0;
+                    i = path.indexOf('%', i + 1)) {
+                char c = decode(path, i);
+                int n = 3;
+                if ((c & 0x80) != 0) {
+                    switch (c >> 4) {
+                        case 0xC:
+                        case 0xD:
+                            n = 6;
+                            c = (char) (((c & 0x1F) << 6)
+                                    | (decode(path, i + 3) & 0x3F));
+                            break;
+                        case 0xE:
+                            n = 9;
+                            c = (char) (((c & 0x0f) << 12)
+                                    | ((decode(path, i + 3) & 0x3F) << 6)
+                                    | (decode(path, i + 6) & 0x3F));
+                            break;
+                        default:
+                            return null;
+                    }
+                }
+                path = path.substring(0, i) + c + path.substring(i + n);
+            }
+        } catch (Exception e) {
+            return null;
+        }
+        return path;
+    }
+
+    /**
+     * Read the request/response and return the initial line.
+     */
+    private static String getInput(Socket sock, boolean isRequest)
+            throws IOException {
+        BufferedInputStream in =
+                new BufferedInputStream(sock.getInputStream(), 256);
+        StringBuffer buf = new StringBuffer(80);
+        do {
+            if (!readLine(in, buf)) {
+                return null;
+            }
+        } while (isRequest && buf.length() == 0);
+        String initial = buf.toString();
+        do {
+            buf.setLength(0);
+        } while (readLine(in, buf) && buf.length() > 0);
+        return initial;
+    }
+
+    @Override
+    public CodebaseContext createContext(String appId) {
+        // Create a context
+        ClassServerCodebaseContext context = new ClassServerCodebaseContext(this, appId);
+        // Assign a context prefix (url-shortened)
+        contexts.put(appId, context);
+        return context;
+    }
+
+    @Override
+    public void destroyContext(CodebaseContext context) {
+        // Remove all the jar mappings.
+        //destroy the context.
+        contexts.remove(context.getAppId());
+    }
+    private static ResourceBundle resources;
+    private static boolean resinit = false;
+
+    /**
+     * Canonicalize the path
+     */
+    private String canon(String path) {
+        if (path.regionMatches(true, 0, "http://", 0, 7)) {
+            int i = path.indexOf('/', 7);
+            if (i < 0) {
+                path = "/";
+            } else {
+                path = path.substring(i);
+            }
+        }
+        path = decode(path);
+        if (path == null || path.length() == 0 || path.charAt(0) != '/') {
+            return null;
+        }
+        return path.substring(1);
+    }
+
+    private String parsePathFromRequest(String req, boolean get) {
+        String path = req.substring(get ? 4 : 5);
+        int i = path.indexOf(' ');
+        if (i > 0) {
+            path = path.substring(0, i);
+        }
+        path = canon(path);
+        return path;
+    }
+
+    private boolean processBadRequest(String[] args, DataOutputStream out) throws IOException {
+        logger.log(Level.FINE, MessageNames.CLASS_SERVER_BAD_REQUEST,
+                args);
+        out.writeBytes("HTTP/1.0 400 Bad Request\r\n\r\n");
+        out.flush();
+        return true;
+    }
+
+    /**
+     * Read specified number of bytes and always close the stream.
+     */
+    private byte[] getBytes(FileObject fo)
+            throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+
+        InputStream in = fo.getContent().getInputStream();
+        int bytesRead = in.read(buffer);
+        while (bytesRead > 0) {
+            out.write(buffer, 0, bytesRead);
+            bytesRead = in.read(buffer);
+        }
+        byte[] bytes = out.toByteArray();
+        out.close();
+        in.close();
+        return bytes;
+    }
+
+    /**
+     * Return the bytes of the requested file, or null if not found.
+     */
+    private byte[] getBytes(String path) throws IOException {
+        FileObject fo = findFileObjectForPath(path);
+        if (fo == null) {
+            return null;
+        }
+        return getBytes(fo);
+    }
+
+    FileObject findFileObjectForPath(String path) {
+        /*
+         * First path segment is appid.
+         */
+        StringTokenizer tok = new StringTokenizer(path, Strings.SLASH, false);
+        FileObject ret = null;
+        try {
+            String appId = tok.nextToken();
+            String jarName = tok.nextToken();
+            ClassServerCodebaseContext context = contexts.get(appId);
+            ret = context.fileEntries.get(jarName);
+        } catch (Throwable t) {
+            logger.log(Level.INFO, MessageNames.CLASS_SERVER_REJECTED_PATH,
+                    path);
+        }
+        return ret;
+    }
+
+    private void writeHeader(DataOutputStream out, byte[] bytes) throws IOException {
+        out.writeBytes("HTTP/1.0 200 OK\r\n");
+        out.writeBytes("Content-Length: " + bytes.length + "\r\n");
+        out.writeBytes("Content-Type: application/java\r\n\r\n");
+    }
+
+    private boolean processRequest(Socket sock) {
+        try {
+            DataOutputStream out =
+                    new DataOutputStream(sock.getOutputStream());
+            String req;
+            try {
+                req = getInput(sock, true);
+            } catch (Exception e) {
+                logger.log(Level.FINE, "reading request", e);
+                return true;
+            }
+            if (req == null) {
+                return true;
+            }
+            String[] args = new String[3];
+            boolean get = req.startsWith("GET ");
+            if (!get && !req.startsWith("HEAD ")) {
+                processBadRequest(args, out);
+            }
+            String path = parsePathFromRequest(req, get);
+            if (path == null) {
+                return processBadRequest(args, out);
+            }
+            if (args != null) {
+                args[0] = path;
+            }
+            args[1] = sock.getInetAddress().getHostName();
+            args[2] = Integer.toString(sock.getPort());
+
+            logger.log(Level.FINER,
+                    get
+                    ? MessageNames.CLASS_SERVER_RECEIVED_REQUEST
+                    : MessageNames.CLASS_SERVER_RECEIVED_PROBE,
+                    args);
+            byte[] bytes;
+            try {
+                bytes = getBytes(path);
+            } catch (Exception e) {
+                logger.log(Level.WARNING, MessageNames.CLASS_SERVER_EXCEPTION_GETTING_BYTES, e);
+                out.writeBytes("HTTP/1.0 500 Internal Error\r\n\r\n");
+                out.flush();
+                return true;
+            }
+            if (bytes == null) {
+                logger.log(Level.FINE, MessageNames.CLASS_SERVER_NO_CONTENT_FOUND, path);
+                out.writeBytes("HTTP/1.0 404 Not Found\r\n\r\n");
+                out.flush();
+                return true;
+            }
+            writeHeader(out, bytes);
+            if (get) {
+                out.write(bytes);
+            }
+            out.flush();
+            return false;
+        } catch (Exception e) {
+            logger.log(Level.FINE, MessageNames.CLASS_SERVER_EXCEPTION_WRITING_RESPONSE, e);
+        } finally {
+            try {
+                sock.close();
+            } catch (IOException e) {
+            }
+        }
+        return false;
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/ClassServerCodebaseContext.java b/river-container-core/src/main/java/org/apache/river/container/codebase/ClassServerCodebaseContext.java
new file mode 100644
index 0000000..928deed
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/ClassServerCodebaseContext.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.codebase;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.vfs2.FileObject;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ClassServerCodebaseContext implements CodebaseContext {
+    ClassServer classServer=null;
+    
+    String appId = null;
+    Map<String, FileObject> fileEntries=new HashMap<String, FileObject>();
+    
+    ClassServerCodebaseContext(ClassServer classServer, String appId) {
+        this.appId = appId;
+        this.classServer=classServer;
+    }
+
+    @Override
+    public String getAppId() {
+        return appId;
+    }
+
+    @Override
+    public void addFile(FileObject file) {
+        
+        /* Add the mapping into our list of objects. */
+        String path=file.getName().getBaseName();
+        fileEntries.put(path, file);
+        /* Force update of the codebase. */
+        codebaseAnnotation=null;
+    }
+
+    private List<URL> codebaseAnnotation=null;
+    
+    @Override
+    public URL[] getCodebaseAnnotation() {
+        try {
+            if (codebaseAnnotation==null) {
+                /*
+                codebase is derived from the list of file objects.
+                */
+                codebaseAnnotation = new ArrayList<URL>();
+                for(String path:fileEntries.keySet()) {
+                    codebaseAnnotation.add(new URL(Strings.HTTP_COLON 
+                            + Strings.SLASH_SLASH 
+                            + classServer.getHost() 
+                            + Strings.COLON
+                            + classServer.getPort() + 
+                            Strings.SLASH
+                            + appId + Strings.SLASH + path));
+                }
+            }
+            return codebaseAnnotation.toArray(new URL[0]);
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/CodebaseContext.java b/river-container-core/src/main/java/org/apache/river/container/codebase/CodebaseContext.java
new file mode 100644
index 0000000..2b66a5f
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/CodebaseContext.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.codebase;
+
+import java.net.URL;
+import org.apache.commons.vfs2.FileObject;
+
+/**
+    Context that interfaces with the codebase handling system to make 
+    codebase files available for remote download, and supply the codebase 
+    annotation that should be used by the classloader.
+ * @author trasukg
+ */
+public interface CodebaseContext {
+    
+    public String getAppId();
+    
+    /**
+    Add the given file into the exported set.
+    @param file 
+    */
+    public void addFile(FileObject file);
+    
+    /**
+    Get a string that represents the codebase annotation that should be returned
+    by the classloader to correspond to this set.
+    @return 
+    */
+    public URL[] getCodebaseAnnotation();
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/CodebaseHandler.java b/river-container-core/src/main/java/org/apache/river/container/codebase/CodebaseHandler.java
new file mode 100644
index 0000000..cee8fd2
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/CodebaseHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.codebase;
+
+/**
+ Interface for a component that makes codebase files available for download.
+ * @author trasukg
+ */
+public interface CodebaseHandler {
+    /**
+    Create a codebase context.
+    @param appId A string that describes the application.  Most likely used
+    for debug or presentation purposes.
+    @return 
+    */
+    public CodebaseContext createContext(String appId);
+    
+    /**
+    Destroy a codebase context, cancelling the provision of all the files
+    contained in that context.
+    @param context 
+    */
+    public void destroyContext(CodebaseContext context);
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/DummyCodebaseHandler.java b/river-container-core/src/main/java/org/apache/river/container/codebase/DummyCodebaseHandler.java
new file mode 100644
index 0000000..831f2f5
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/DummyCodebaseHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.codebase;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.vfs2.FileObject;
+
+/**
+ *
+ * @author trasukg
+ */
+public class DummyCodebaseHandler implements CodebaseHandler {
+
+    @Override
+    public CodebaseContext createContext(String appId) {
+        return new DummyContext(appId);
+    }
+
+    @Override
+    public void destroyContext(CodebaseContext context) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+    
+    private class DummyContext implements CodebaseContext {
+        private String appId;
+        
+        private DummyContext(String appId) {
+            this.appId=appId;
+        }
+        
+        List<URL> urls=new ArrayList<URL>();
+        
+        @Override
+        public void addFile(FileObject file) {
+            try {
+            urls.add(new URL("http://unknown.com/" + file.getName().getBaseName()));
+            } catch(Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+        
+        @Override
+        public URL[] getCodebaseAnnotation() {
+            return urls.toArray(new URL[0]);
+        }
+
+        @Override
+        public String getAppId() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+        
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/Strings.java b/river-container-core/src/main/java/org/apache/river/container/codebase/Strings.java
new file mode 100644
index 0000000..753de46
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/Strings.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.codebase;
+
+/**
+ *
+ * @author trasukg
+ */
+public class Strings {
+    public static final String
+            CLASS_SERVER_PROPERTIES="class-server.properties",
+            COLON=":",
+            DUMMY_ANNOTATION="http://www.demo.com/unknown.jar",
+            HTTP_COLON="http:",
+            INITIAL_PORT="initialPort",
+            SLASH="/",
+            SLASH_SLASH="//";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/codebase/UUIDEncoder.java b/river-container-core/src/main/java/org/apache/river/container/codebase/UUIDEncoder.java
new file mode 100644
index 0000000..a2ce6a7
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/codebase/UUIDEncoder.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.codebase;
+
+import java.util.UUID;
+
+/**
+ * Class encodes a 128-bit UUID into a shortened form suitable for use
+    as a url.
+ * @author trasukg
+ */
+public class UUIDEncoder {
+    public String encode(UUID uuid) {
+        return uuid.toString();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/core-config.xml b/river-container-core/src/main/java/org/apache/river/container/core-config.xml
new file mode 100644
index 0000000..bb2667d
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/core-config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Document   : config.xml
+    Created on : December 10, 2010, 6:39 PM
+    Author     : trasukg
+    Description:
+        This is the core configuration that is processed by the
+        Bootstrap system prior to processing the profile's config.xml
+
+        Items defined in this config are subject to being overwritten by the
+        profile's config (e.g. classpath or properties settings).  As such,
+        this is a good place to set defaults.
+
+        It also gives a spot to include elements that are required in every
+        profile, for instance the AnnotatedClassDeployer and MBeanRegistrar.
+-->
+
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+ -->
+
+<cfg:container-config  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+   xmlns:cfg='http://river.apache.org/xml/ns/container/config/1.0'
+   xsi:schemaLocation='http://river.apache.org/xml/ns/container/config/1.0 file:/home/trasukg/development/surrogate/src/schemas/config.xsd'>
+    <!-- Dynamic policy provider needs to be available to both the container and
+    any "application" classloaders, so it needs to be in a classloader which is
+    a parent to both the container classloader and the application loaders.
+    In traditional Jini usage, jsk-policy.jar was put into the extensions directory,
+    hence was available in the "extensions" loader.  Here, we'd prefer not to 
+    require any changes to the JSDK installation directory.
+    -->
+    <cfg:classpath id="systemClassLoader">lib/jsk-policy.jar</cfg:classpath>
+    <cfg:classpath id="containerClassLoader" parent="systemClassLoader">
+        lib/RiverSurrogate.jar
+        lib/commons-logging-1.1.1.jar
+        lib/commons-vfs-1.0.jar
+        lib/jsk-platform.jar
+        lib/jsk-resources.jar
+    </cfg:classpath>
+
+    <!--cfg:component class="org.apache.river.container.ShowContextToConsole"/-->
+    <cfg:component class="org.apache.river.container.AnnotatedClassDeployer"/>
+
+    <cfg:component class="org.apache.river.container.security.SecurityInitializer"/>
+    
+    <cfg:component class="org.apache.river.container.el.ArgsParserImpl"/>
+    
+    <cfg:property name="deploymentDirectory" value="deploy"/>
+
+    <cfg:component class="org.apache.river.container.SystemClassloaderInitializer"/>
+    <cfg:component class="org.apache.river.container.CommandLineArgumentParser"/>
+    <cfg:component class="org.apache.river.container.MBeanRegistrar"/>
+    <cfg:component class="org.apache.river.container.ShutdownListener"/>
+    <cfg:component class="org.apache.river.container.FileUtilityImpl"
+        name="fileUtility"/>
+    <cfg:component class="org.apache.river.container.PropertiesFileReader"/>
+    <cfg:component class="org.apache.river.container.ProfileConfigReader"/>
+
+</cfg:container-config>
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/ASTNode.java b/river-container-core/src/main/java/org/apache/river/container/deployer/ASTNode.java
new file mode 100644
index 0000000..c558873
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/ASTNode.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.deployer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ASTNode extends SimpleNode {
+
+    private Object value=null;
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    
+    public ASTNode(int i) {
+        super(i);
+    }
+
+    public ASTNode(DeployerConfigParser p, int i) {
+        super(p, i);
+    }
+
+    public String getName() {
+        return DeployerConfigParserTreeConstants.jjtNodeName[id];
+    }
+
+    public String toString() {
+        if (id==DeployerConfigParserTreeConstants.JJTSYMBOL) {
+            return getValue().toString();
+        }
+        if (id==DeployerConfigParserTreeConstants.JJTLITERAL) {
+            Object o=getValue();
+            if (o instanceof String) {
+                return "\"" + o.toString() + "\"";
+            }
+            return o.toString();
+        }
+        String childList = childList();
+        if (!Strings.EMPTY.equals(childList)) {
+            return getName() + " " + childList;
+        } else {
+            return getName();
+        }
+    }
+
+    public String childList() {
+        StringBuffer sb = new StringBuffer();
+        if (jjtGetNumChildren() != 0) {
+            boolean first = true;
+            for (int i = 0; i < jjtGetNumChildren(); i++) {
+                if (!first) {
+                    sb.append(" ");
+                } else {
+                    first = false;
+                }
+                String childStr = jjtGetChild(i).toString();
+                if (childStr.indexOf(Strings.SPACE) != -1) {
+                    sb.append(Strings.LPAREN);
+                    sb.append(childStr);
+                    sb.append(Strings.RPAREN);
+                } else {
+                    sb.append(childStr);
+                }
+            }
+        }
+        return sb.toString();
+    }
+    
+    public List<ASTNode> search(Class[] path) {
+        List<ASTNode> matches=new ArrayList<ASTNode>();
+        search(path, matches);
+        return matches;
+    }
+    
+    public void search(Class[] path, List<ASTNode> matches) {
+        if (path.length==0) {
+            return;
+        }
+        if (!this.getClass().equals(path[0])){
+            return;
+        }
+        if (path.length==1) {
+            matches.add(this);
+            return;
+        }
+        path=Arrays.copyOfRange(path, 1, path.length);
+        for (int i=0; i < this.jjtGetNumChildren(); i++) {
+            ((ASTNode) jjtGetChild(i)).search(path, matches);
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/ApplicationEnvironment.java b/river-container-core/src/main/java/org/apache/river/container/deployer/ApplicationEnvironment.java
new file mode 100644
index 0000000..dc9dab3
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/ApplicationEnvironment.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.deployer;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.river.container.classloading.VirtualFileSystemClassLoader;
+import org.apache.river.container.codebase.CodebaseContext;
+import org.apache.river.container.work.WorkingContext;
+
+/**
+ * Everything the host needs to know about the surrogate.
+ * @author trasukg
+ */
+public class ApplicationEnvironment {
+    VirtualFileSystemClassLoader classLoader=null;
+
+    String serviceName=null;
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public void setServiceName(String serviceName) {
+        this.serviceName = serviceName;
+    }
+    FileObject serviceArchive=null;
+    FileObject serviceRoot=null;
+    CodebaseContext codebaseContext=null;
+
+    public VirtualFileSystemClassLoader getClassLoader() {
+        return classLoader;
+    }
+
+    public void setClassLoader(VirtualFileSystemClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public FileObject getServiceArchive() {
+        return serviceArchive;
+    }
+
+    public void setServiceArchive(FileObject serviceArchive) {
+        this.serviceArchive = serviceArchive;
+    }
+
+    public FileObject getServiceRoot() {
+        return serviceRoot;
+    }
+
+    public void setServiceRoot(FileObject serviceRoot) {
+        this.serviceRoot = serviceRoot;
+    }
+
+    public CodebaseContext getCodebaseContext() {
+        return codebaseContext;
+    }
+
+    public void setCodebaseContext(CodebaseContext codebaseContext) {
+        this.codebaseContext = codebaseContext;
+    }
+    
+    Object serviceInstance = null;
+
+    public Object getServiceInstance() {
+        return serviceInstance;
+    }
+
+    public void setServiceInstance(Object serviceInstance) {
+        this.serviceInstance = serviceInstance;
+    }
+    
+    WorkingContext workingContext=null;
+
+    public WorkingContext getWorkingContext() {
+        return workingContext;
+    }
+
+    public void setWorkingContext(WorkingContext workingContext) {
+        this.workingContext = workingContext;
+    }
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/ClasspathFilterBuilder.java b/river-container-core/src/main/java/org/apache/river/container/deployer/ClasspathFilterBuilder.java
new file mode 100644
index 0000000..b3fbc1a
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/ClasspathFilterBuilder.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.deployer;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+import org.apache.river.container.LocalizedRuntimeException;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.classloading.Acceptor;
+import org.apache.river.container.classloading.AllAcceptor;
+import org.apache.river.container.classloading.ClasspathFilter;
+import org.apache.river.container.classloading.ResourceAcceptor;
+import org.apache.river.container.classloading.VirtualFileSystemClassLoader;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ClasspathFilterBuilder {
+
+    private static final Logger log = Logger.getLogger(ClasspathFilterBuilder.class.getName());
+
+    public static List<ClasspathFilter> parseToFilters(String input) {
+        try {
+            ASTclasspath expression = classpathExpressionFromString(input);
+            List<ClasspathFilter> filters = filtersFromClasspathExpression(expression);
+            return filters;
+        } catch (ParseException ex) {
+            throw new LocalizedRuntimeException(MessageNames.BUNDLE_NAME,
+                    MessageNames.BAD_CLASSPATH_EXPR,
+                    new Object[]{input, ex.getMessage()});
+        }
+    }
+
+    private static ASTclasspath classpathExpressionFromString(String input) throws ParseException {
+        Reader r = new StringReader(input);
+        DeployerConfigParser parser = new DeployerConfigParser(r);
+        parser.classpath();
+        ASTclasspath expression = (ASTclasspath) parser.jjtree.popNode();
+        return expression;
+    }
+
+    public static List<ClasspathFilter> filtersFromClasspathExpression(ASTclasspath expression) {
+        List<ClasspathFilter> filters = new ArrayList<ClasspathFilter>();
+        for (int i = 0; i < expression.jjtGetNumChildren(); i++) {
+            ASTcpEntry clause = (ASTcpEntry) expression.jjtGetChild(i);
+            ClasspathFilter cpf = makeFilter(clause);
+            filters.add(cpf);
+        }
+        return filters;
+    }
+
+    public static ClasspathFilter makeFilter(ASTcpEntry expression) {
+        /* First node is the jar name.  Subsequent nodes are the filter
+        conditions.
+         */
+        ClasspathFilter cpf = new ClasspathFilter();
+        cpf.setJarName(expression.jjtGetChild(0).toString());
+        for (int i = 1; i < expression.jjtGetNumChildren(); i++) {
+            Node node = expression.jjtGetChild(i);
+            if (node instanceof ASTsymbol) {
+                String resourceName = VirtualFileSystemClassLoader.classToResourceName(node.toString());
+                log.fine("Building ResourceAcceptor with string '" + resourceName + "'");
+                Acceptor acc = new ResourceAcceptor(resourceName);
+                cpf.getAcceptors().add(acc);
+            }
+            if (node instanceof ASTliteral) {
+                log.fine("Building ResourceAcceptor with string '" + node.toString() + "'");
+                ASTliteral lNode=(ASTliteral) node;
+                Acceptor acc = new ResourceAcceptor((String)lNode.getValue());
+                cpf.getAcceptors().add(acc);
+            }
+        }
+        /* If there were no filter clauses, hence no acceptors, allow all
+        patterns.
+         */
+        if (cpf.getAcceptors().isEmpty()) {
+            cpf.getAcceptors().add(new AllAcceptor());
+        }
+        return cpf;
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/ServiceLifeCycle.java b/river-container-core/src/main/java/org/apache/river/container/deployer/ServiceLifeCycle.java
new file mode 100644
index 0000000..b7c39a9
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/ServiceLifeCycle.java
@@ -0,0 +1,38 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.deployer;

+

+import javax.management.MXBean;

+

+/**

+ * Life cycle state controller interface for a Starter Service application.

+ * @author trasukg

+ */

+@MXBean

+public interface ServiceLifeCycle {

+    public String getName();

+    

+    public void start();

+    

+    public void stop();

+    

+    public void prepare();

+    

+    public String getStatus();

+    

+}

diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java
new file mode 100644
index 0000000..9d5ad17
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java
@@ -0,0 +1,473 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* TODO: - Complete deployment of items that are in the deployment
+ directory.
+ */
+package org.apache.river.container.deployer;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.security.policy.DynamicPolicyProvider;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.river.container.ConfigurationException;
+import org.apache.river.container.Context;
+import org.apache.river.container.FileUtility;
+import org.apache.river.container.Init;
+import org.apache.river.container.Injected;
+import org.apache.river.container.InjectionStyle;
+import org.apache.river.container.LocalizedRuntimeException;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.Name;
+import org.apache.river.container.PropertiesFileReader;
+import org.apache.river.container.Strings;
+import org.apache.river.container.Utils;
+import org.apache.river.container.classloading.ClasspathFilter;
+import org.apache.river.container.classloading.VirtualFileSystemClassLoader;
+import org.apache.river.container.codebase.CodebaseContext;
+import org.apache.river.container.codebase.CodebaseHandler;
+import org.apache.river.container.el.ArgsParser;
+import org.apache.river.container.liaison.VirtualFileSystemConfiguration;
+import org.apache.river.container.work.ContextualWorkManager;
+import org.apache.river.container.work.WorkManager;
+
+/**
+ * Deployer that instantiates applications or services based on the
+ * com.sun.jini.starter API
+ */
+public class StarterServiceDeployer implements StarterServiceDeployerMXBean {
+
+    private static final Logger log =
+            Logger.getLogger(StarterServiceDeployer.class.getName(), MessageNames.BUNDLE_NAME);
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private FileUtility fileUtility = null;
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private Context context;
+    @Name
+    private String myName = null;
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private CodebaseHandler codebaseHandler = null;
+    
+    private String config = Strings.STARTER_SERVICE_DEPLOYER_CONFIG;
+    private ASTconfig configNode = null;
+
+    public String getConfig() {
+        return config;
+    }
+
+    public void setConfig(String config) {
+        this.config = config;
+    }
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private PropertiesFileReader propertiesFileReader = null;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private ArgsParser argsParser = null;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    WorkManager workManager = null;
+    
+    @Injected(style=InjectionStyle.BY_TYPE)
+    ContextualWorkManager contextualWorkManager=null;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private DynamicPolicyProvider securityPolicy = null;
+
+    public void addPlatformCodebaseJars(CodebaseContext codebaseContext) throws IOException {
+        ASTcodebase codebaseNode = (ASTcodebase) configNode.search(new Class[]{
+                    ASTconfig.class, ASTclassloader.class, ASTcodebase.class
+                }).get(0);
+        /*
+         Register the platform codebase jars with the codebase service.
+         */
+        for (int i = 0; i < codebaseNode.jjtGetNumChildren(); i++) {
+            String jarFile = codebaseNode.jjtGetChild(i).toString();
+            FileObject fo = fileUtility.getLibDirectory().resolveFile(jarFile);
+            codebaseContext.addFile(fo);
+            log.log(Level.FINE, MessageNames.ADDED_PLATFORM_CODEBASE_JAR,
+                    jarFile);
+        }
+    }
+
+    public String[] constructArgs(String argLine) {
+        String[] args = null;
+        if (argLine == null) {
+            args = new String[0];
+        } else {
+            args = argsParser.toArgs(argLine);
+        }
+        return args;
+    }
+
+    public VirtualFileSystemClassLoader createServiceClassloader(FileObject serviceRoot, CodeSource codeSource) throws IOException, FileSystemException {
+
+        String parentLoaderName = configNode.search(
+                new Class[]{ASTconfig.class, ASTclassloader.class, ASTparent.class}).get(0).jjtGetChild(0).toString();
+        log.log(Level.FINE, MessageNames.SERVICE_PARENT_CLASSLOADER_IS, parentLoaderName);
+        ClassLoader parentLoader = (ClassLoader) context.get(parentLoaderName);
+        VirtualFileSystemClassLoader cl =
+                createChildOfGivenClassloader(parentLoader, codeSource);
+        /*
+         Include platform jars from the container's lib directory.
+         */
+        ASTclasspath platformJarSpec = (ASTclasspath) configNode.search(new Class[]{ASTconfig.class,
+                    ASTclassloader.class, ASTjars.class, ASTclasspath.class}).get(0);
+        addPlatformJarsToClassloader(platformJarSpec, cl);
+        addLibDirectoryJarsToClasspath(serviceRoot, cl);
+
+        return cl;
+
+    }
+
+    protected void addLibDirectoryJarsToClasspath(FileObject serviceRoot, VirtualFileSystemClassLoader cl) throws FileSystemException {
+        /*
+         Add the jar files from the service's 'lib' directory.
+         */
+        FileObject libDir = serviceRoot.resolveFile(Strings.LIB);
+        List<FileObject> jarFiles = Utils.findChildrenWithSuffix(libDir,
+                Strings.DOT_JAR);
+        for (FileObject jarFile : jarFiles) {
+            cl.addClassPathEntry(libDir, jarFile.getName().getBaseName());
+        }
+    }
+
+    protected void addPlatformJarsToClassloader(ASTclasspath platformJarSpec, VirtualFileSystemClassLoader cl) throws IOException, LocalizedRuntimeException {
+        log.log(Level.FINE, MessageNames.ADDING_CLASSPATH_ENTRY, new Object[]{platformJarSpec.toString()});
+        List<ClasspathFilter> filters = ClasspathFilterBuilder.filtersFromClasspathExpression(platformJarSpec);
+
+        cl.addClasspathFilters(filters, fileUtility.getLibDirectory());
+    }
+
+    protected VirtualFileSystemClassLoader createChildOfGivenClassloader(ClassLoader parent, CodeSource codeSource) {
+        /*
+         Create the service classloader.
+         */
+        VirtualFileSystemClassLoader cl =
+                new VirtualFileSystemClassLoader(null, parent, codeSource);
+        return cl;
+    }
+
+    public void exportServiceCodebaseJars(FileObject serviceRoot, CodebaseContext codebaseContext) throws FileSystemException {
+        /*
+         Register the service's codebase jars with the codebase service.
+         */
+        FileObject libDlDir = serviceRoot.resolveFile(Strings.LIB_DL);
+        List<FileObject> dljarFiles = Utils.findChildrenWithSuffix(libDlDir,
+                Strings.DOT_JAR);
+        for (FileObject jarFile : dljarFiles) {
+            codebaseContext.addFile(jarFile);
+        }
+    }
+
+    @Init
+    public void init() {
+        try {
+            tryInitialize();
+        } catch (Throwable ex) {
+            log.log(Level.SEVERE, MessageNames.STARTER_SERVICE_DEPLOYER_FAILED_INIT,
+                    ex);
+            throw new ConfigurationException(ex,
+                    MessageNames.STARTER_SERVICE_DEPLOYER_FAILED_INIT);
+        }
+    }
+
+    public void launchService(final ApplicationEnvironment env, Properties startProps, final String[] args) {
+        final String startClassName = startProps.getProperty(Strings.START_CLASS);
+        /*
+         Launch the service.
+         */
+        log.log(Level.FINE, MessageNames.CALLING_MAIN, new Object[]{
+                    startClassName, Utils.format(args)
+                });
+        Runnable task = new Runnable() {
+
+            @Override
+            public void run() {
+                try {
+                    env.setServiceInstance(instantiateService(env.getClassLoader(), startClassName, args));
+                } catch (Exception ex) {
+                    throw new RuntimeException(ex);
+                }
+            }
+        };
+        env.getWorkingContext().getWorkManager().queueTask(env.getClassLoader(), task);
+    }
+
+    public Properties readStartProperties(FileObject serviceRoot) throws FileSystemException, LocalizedRuntimeException, IOException {
+        /*
+         Read the start.properties file.
+         */
+        FileObject startProperties = serviceRoot.resolveFile(Strings.START_PROPERTIES);
+        if (startProperties == null || !startProperties.getType().equals(FileType.FILE)
+                || !startProperties.isReadable()) {
+            throw new LocalizedRuntimeException(MessageNames.BUNDLE_NAME,
+                    MessageNames.CANT_READ_START_PROPERTIES,
+                    new Object[]{Strings.START_PROPERTIES,
+                        serviceRoot.getName().getBaseName()});
+        }
+        Properties startProps = propertiesFileReader.getProperties(startProperties);
+        return startProps;
+    }
+
+    public void setupLiaisonConfiguration(FileObject serviceArchive, FileObject serviceRoot, VirtualFileSystemClassLoader cl) throws ConfigurationException {
+        /*
+         Setup the liaison configuration.
+         */
+        try {
+            File workingDir = null;
+            if (serviceArchive != null) {
+                workingDir = new File(serviceArchive.getURL().toURI());
+            } else {
+                workingDir = new File(serviceRoot.getURL().toURI());
+
+            }
+            grantPermissions(cl,
+                    new Permission[]{new FilePermission(workingDir.getAbsolutePath(), Strings.READ)});
+            Utils.logClassLoaderHierarchy(log, Level.FINE, this.getClass());
+            String configName = VirtualFileSystemConfiguration.class.getName();
+            invokeStatic(cl, configName,
+                    Strings.SET_WORKING_DIRECTORY,
+                    workingDir);
+            /*
+             Setup the "special" variables in the configuration.
+             */
+            ASTconfiguration configurationNode = (ASTconfiguration) configNode.search(new Class[]{ASTconfig.class, ASTconfiguration.class}).get(0);
+            for (int i = 0; i < configurationNode.jjtGetNumChildren(); i++) {
+                ASTconfigEntry cfgEntryNode = (ASTconfigEntry) configurationNode.jjtGetChild(i);
+                String varName = cfgEntryNode.jjtGetChild(0).toString();
+                String contextVarName = cfgEntryNode.jjtGetChild(1).toString();
+                Object contextValue = context.get(contextVarName);
+                if (contextValue != null) {
+                    invokeStatic(cl, configName, 
+                            Strings.PUT_SPECIAL_ENTRY, 
+                            new Class[] {String.class, Object.class}, 
+                            Strings.DOLLAR + varName, contextValue);
+                } else {
+                    log.log(Level.WARNING, MessageNames.MISSING_SPECIAL_VALUE, 
+                            new Object[] {getConfig(), varName, contextVarName});
+                } 
+            }
+        } catch (Exception ex) {
+            log.log(Level.WARNING, MessageNames.EXCEPTION_THROWN, Utils.stackTrace(ex));
+            throw new ConfigurationException(ex,
+                    MessageNames.STARTER_SERVICE_DEPLOYER_FAILED_INIT);
+        }
+    }
+
+    private void tryInitialize() throws IOException, ParseException {
+        log.log(Level.FINE, MessageNames.STARTER_SERVICE_DEPLOYER_STARTING, myName);
+        /*
+         Read and parse the configuration file.
+         */
+
+        FileObject configFile = fileUtility.getProfileDirectory().resolveFile(config);
+        InputStream in = configFile.getContent().getInputStream();
+        configNode = DeployerConfigParser.parseConfig(in);
+        log.log(Level.FINE,MessageNames.STARTER_SERVICE_DEPLOYER_INITIALIZED, 
+                new Object[] {myName} );
+    }
+
+    public ServiceLifeCycle deployServiceArchive(FileObject serviceArchive) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
+        // Create an application environment
+        ApplicationEnvironment env = new ApplicationEnvironment();
+        env.setServiceArchive(serviceArchive);
+        env.setServiceRoot(
+                serviceArchive.getFileSystem().getFileSystemManager().createFileSystem(Strings.JAR, serviceArchive));
+        String serviceName = findServiceName(env.getServiceArchive(), env.getServiceRoot());
+        env.setServiceName(serviceName);
+        ServiceLifeCycle slc=StarterServiceLifeCycleSM.newStarterServiceLifeCycle(env, this);
+        return slc;
+    }
+
+    private String findServiceName(FileObject serviceArchive, FileObject serviceRoot) {
+        if (serviceArchive != null) {
+            return serviceArchive.getName().getBaseName();
+        }
+        return serviceRoot.getName().getBaseName();
+    }
+
+    private URL findServiceURL(FileObject serviceArchive, FileObject serviceRoot) throws FileSystemException {
+        if (serviceArchive != null) {
+            return serviceArchive.getURL();
+        }
+        return serviceRoot.getURL();
+    }
+
+    void prepareService(ApplicationEnvironment env) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
+        
+        CodeSource serviceCodeSource =
+                new CodeSource(findServiceURL(env.getServiceArchive(), env.getServiceRoot()),
+                new Certificate[0]);
+        log.log(Level.INFO, MessageNames.CODESOURCE_IS,
+                new Object[]{env.getServiceName(), serviceCodeSource});
+        VirtualFileSystemClassLoader cl = createServiceClassloader(env.getServiceRoot(), serviceCodeSource);
+        env.setClassLoader(cl);
+        
+        /*
+         Create a codebase context.
+         */
+        CodebaseContext codebaseContext =
+                codebaseHandler.createContext(env.getServiceName());
+        env.setCodebaseContext(codebaseContext);
+        addPlatformCodebaseJars(codebaseContext);
+        exportServiceCodebaseJars(env.getServiceRoot(), codebaseContext);
+
+        /*
+         Setup the classloader's codebase annotation.
+         */
+        cl.setCodebase(codebaseContext.getCodebaseAnnotation());
+        /*
+         Grant the appropriate permissions to the service's classloader and
+         protection domain.
+         */
+        Permission[] perms = createPermissionsInClassloader(cl);
+        grantPermissions(cl, perms);
+        setupLiaisonConfiguration(env.getServiceArchive(), env.getServiceRoot(), cl);
+        
+        /*
+         * Create a working context (work manager).
+         */
+        env.setWorkingContext(contextualWorkManager.createContext(env.getServiceName()));
+    }
+    
+    void launchService(ApplicationEnvironment env) throws FileSystemException, IOException {
+        Properties startProps = readStartProperties(env.getServiceRoot());
+        String argLine = startProps.getProperty(Strings.START_PARAMETERS);
+        final String[] args = constructArgs(argLine);
+
+        launchService(env, startProps, args);
+        log.log(Level.INFO, MessageNames.COMPLETED_SERVICE_DEPLOYMENT, env.getServiceName());
+    }
+
+    Permission[] createPermissionsInClassloader(ClassLoader cl) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
+        List<Permission> perms = new ArrayList<Permission>();
+        // Get all the permission nodes from the config.
+        Class[] path = new Class[]{ASTconfig.class, ASTgrant.class, ASTpermission.class};
+        List<ASTNode> permNodes = configNode.search(path);
+        // Create a permission for each
+        for (ASTNode node : permNodes) {
+            String className = (String) ((ASTsymbol) node.jjtGetChild(0)).getValue();
+            Object permissionConstructorArgs[] = new String[node.jjtGetNumChildren() - 1];
+            for (int i = 0; i < permissionConstructorArgs.length; i++) {
+                permissionConstructorArgs[i] = (String) ((ASTliteral) node.jjtGetChild(i + 1)).getValue();
+            }
+            Permission perm = (Permission) invokeConstructor(cl, className, permissionConstructorArgs);
+            perms.add(perm);
+        }
+        return perms.toArray(new Permission[0]);
+    }
+
+    private Object invokeStatic(ClassLoader cl, String className, String methodName,
+            Object... parms) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        Class clazz = Class.forName(className, true, cl);
+        Class[] parameterTypes = new Class[parms.length];
+        for (int i = 0; i < parms.length; i++) {
+            parameterTypes[i] = parms[i].getClass();
+        }
+        Method method = clazz.getMethod(methodName, parameterTypes);
+        return method.invoke(null, parms);
+    }
+
+    private Object invokeStatic(ClassLoader cl, String className, String methodName, Class[] argTypes,
+            Object... parms) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        Class clazz = Class.forName(className, true, cl);
+        Method method = clazz.getMethod(methodName, argTypes);
+        return method.invoke(null, parms);
+    }
+
+    private Object invokeConstructor(ClassLoader cl, String className,
+            Object... parms) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
+        Class clazz = Class.forName(className, true, cl);
+        Class[] parameterTypes = new Class[parms.length];
+        for (int i = 0; i < parms.length; i++) {
+            parameterTypes[i] = parms[i].getClass();
+        }
+        Constructor method = clazz.getConstructor(parameterTypes);
+        return method.newInstance(parms);
+    }
+
+    private void grantPermissions(ClassLoader cl, Permission[] perms) {
+        try {
+            Class clazz = Class.forName(VirtualFileSystemConfiguration.class.getName(), true, cl);
+            securityPolicy.grant(clazz, new Principal[0], perms);
+
+        } catch (Throwable t) {
+            throw new ConfigurationException(MessageNames.FAILED_DEPLOY_SERVICE, t);
+        }
+    }
+
+    private Object instantiateService(ClassLoader cl, String className, String[] parms)
+            throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, InstantiationException {
+        Class clazz = Class.forName(className, true, cl);
+        log.log(Level.FINE, MessageNames.CLASSLOADER_IS,
+                new Object[]{clazz.getName(), clazz.getClassLoader().toString()});
+
+        // Get this through dynamic lookup becuase it won't be in the parent
+        // classloader!
+        Class lifeCycleClass = Class.forName(Strings.LIFECYCLE_CLASS, true, cl);
+        Constructor[] constructors = clazz.getDeclaredConstructors();
+        System.out.println("Class is " + clazz);
+        for (int i = 0; i < constructors.length; i++) {
+            Constructor constructor = constructors[i];
+            System.out.println("Found constructor " + constructor + " on " + className);
+        }
+        Constructor constructor = clazz.getDeclaredConstructor(new Class[]{String[].class, lifeCycleClass});
+        constructor.setAccessible(true);
+        return constructor.newInstance(parms, null);
+    }
+    
+    /**
+     * Attempt to stop the service in an orderly fashion.
+     * Go to the service, see if it implements Administrable, then get the 
+     * admin proxy and see if it implements DestroyAdmin.  If so, call it.
+     * @param env 
+     */
+    public void stopService(ApplicationEnvironment env)  {
+        /* Option 1 - Service has a getAdmin() method - it probably implements
+         * Administrable.
+         */
+        Object serviceInstance=env.getServiceInstance();
+        Method getAdmin=null;
+        try {
+            getAdmin=serviceInstance.getClass().getMethod(Strings.GET_ADMIN, new Class[0]);
+        } catch (Exception ex) {
+            // Silent catch - leave it null;
+        }
+        if (getAdmin != null) {
+            
+        }
+        
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployerMXBean.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployerMXBean.java
new file mode 100644
index 0000000..b091746
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployerMXBean.java
@@ -0,0 +1,26 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.deployer;

+

+/**

+ *

+ * @author trasukg

+ */

+public interface StarterServiceDeployerMXBean {

+

+}

diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceLifeCycleSM.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceLifeCycleSM.java
new file mode 100644
index 0000000..94a55eb
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceLifeCycleSM.java
@@ -0,0 +1,297 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.deployer;

+

+import java.util.ArrayList;

+import java.util.List;

+import java.util.concurrent.ScheduledFuture;

+import java.util.concurrent.TimeUnit;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.apache.river.container.MessageNames;

+import org.apache.river.container.Utils;

+import org.apache.river.container.hsm.Controller;

+import org.apache.river.container.hsm.Guard;

+import org.apache.river.container.hsm.Initial;

+import org.apache.river.container.hsm.OnEntry;

+import org.apache.river.container.hsm.OnExit;

+import org.apache.river.container.hsm.PlainStateMachineExecutor;

+import org.apache.river.container.hsm.RootState;

+import org.apache.river.container.hsm.State;

+import org.apache.river.container.hsm.StateMachineInfo;

+import org.apache.river.container.hsm.Transition;

+

+/**

+ * Life cycle controller for "service-starter" services. Idle --> Starting -->

+ * Running --> Stopping --> Idle --> Zombie

+ */

+@RootState({ServiceLifeCycle.class, StatusEvents.class})

+public class StarterServiceLifeCycleSM {

+

+    public static final int MAX_RETRY_COUNT=10;

+    

+    private static final Logger logger = Logger.getLogger(StarterServiceLifeCycleSM.class.getName(),

+            MessageNames.BUNDLE_NAME);

+    private ApplicationEnvironment appEnv = null;

+    private StarterServiceDeployer deployer = null;

+    private StatusEvents eventProxy = null;

+    private ServiceLifeCycle lifeCycleProxy = null;

+    private List<Throwable> exceptions = new ArrayList<Throwable>();

+

+    public static ServiceLifeCycle newStarterServiceLifeCycle(ApplicationEnvironment appEnv, StarterServiceDeployer deployer) {

+        StarterServiceLifeCycleSM machine = new StarterServiceLifeCycleSM();

+        machine.appEnv = appEnv;

+        machine.deployer = deployer;

+        machine.eventProxy = (StatusEvents) PlainStateMachineExecutor.createProxy(machine);

+        machine.lifeCycleProxy = (ServiceLifeCycle) machine.eventProxy;

+        return machine.lifeCycleProxy;

+    }

+    @State({Idle.class, Preparing.class, Prepared.class, Starting.class, 

+        Failed.class, Running.class, Stopping.class, DirtyShutdown.class,

+        Idle.class})

+    @Initial(Idle.class)

+    private Object state;

+    @Controller

+    private StateMachineInfo controller;

+

+    public String getStatus() {

+        return state.getClass().getSimpleName();

+    }

+

+    public String getName() {

+        return appEnv.getServiceName();

+    }

+

+    public void start() {

+        logger.log(Level.FINE,MessageNames.RECEIVED_START,

+                new String[]{ getStatus() });

+    }

+    

+    public class Idle {

+        /*

+         * To start from idle means to prepare, and then start.

+         */

+

+        @Transition(Preparing.class)

+        public void start() {

+            exceptions.clear();

+            Runnable command = new Runnable() {

+                public void run() {

+                    /* Prepare the application environment. */

+                    try {

+                        deployer.prepareService(appEnv);

+                        eventProxy.prepareSucceeded();

+                        lifeCycleProxy.start();

+                    } catch (Exception ex) {

+                        eventProxy.exception(ex);

+                    }

+                }

+            };

+            deployer.workManager.queueTask(null, command);

+        }

+

+        @Transition(Preparing.class)

+        public void prepare() {

+            exceptions.clear();

+            Runnable command = new Runnable() {

+                public void run() {

+                    /* Prepare the application environment. */

+                    try {

+                        deployer.prepareService(appEnv);

+                        eventProxy.prepareSucceeded();

+                    } catch (Exception ex) {

+                        eventProxy.exception(ex);

+                    }

+                }

+            };

+            deployer.workManager.queueTask(null, command);

+        }

+

+    }

+

+    public class Preparing {

+

+        @Transition(Prepared.class)

+        public void prepareSucceeded() {

+        }

+

+        @Transition(Failed.class)

+        public void exception(Exception ex) {

+            exceptions.add(ex);

+        }

+

+    }

+

+    public class Prepared {

+

+        @Transition(Starting.class)

+        public void start() {

+            Runnable command = new Runnable() {

+                public void run() {

+                    /* Prepare the application environment. */

+                    try {

+                        deployer.launchService(appEnv);

+                        eventProxy.startSucceeded();

+                    } catch (Exception ex) {

+                        eventProxy.exception(ex);

+                    }

+                }

+            };

+            deployer.workManager.queueTask( null, command);

+

+        }

+        

+        @Transition(Idle.class)

+        public void stop() {}

+        

+    }

+

+    public class Running {

+        @Transition(Stopping.class) 

+        public void stop() {

+             Runnable command = new Runnable() {

+                public void run() {

+                    /* Prepare the application environment. */

+                    try {

+                        deployer.stopService(appEnv);

+                        if(appEnv.getWorkingContext().getActiveThreadCount()==0) {

+                            eventProxy.stopSucceeded();

+                        } else {

+                            eventProxy.stopFailed();

+                        }

+                    } catch (Exception ex) {

+                        eventProxy.exception(ex);

+                    }

+                }

+            };

+            deployer.workManager.queueTask(null, command);

+        }

+    }

+

+    /**

+     * We want the state to show as "Failed" but in reality, you can do all

+     * the same commands as if you were in "Idle".  So we just extend "Idle".

+     */

+    public class Failed extends Idle {

+    }

+

+    public class Stopping {

+        /* TODO: Implement the state machine from here to check for proper

+         * shutdown.

+         */

+        

+        @Transition(Idle.class) 

+        public void stopSucceeded() {}

+        

+        @Transition(DirtyShutdown.class)

+        public void stopFailed() {}

+        

+        @Guard(Idle.class) 

+        public boolean areThreadsGone() {

+            return appEnv.getWorkingContext().getActiveThreadCount()==0;

+        }

+        

+        public void exception(Exception ex) {

+            logger.log(Level.WARNING, MessageNames.EXCEPTION_WHILE_STOPPING,

+                    new Object[] { Utils.stackTrace(ex) });

+        }

+    }

+

+    public class DirtyShutdown {

+        int retryCount=0;

+        

+        @OnEntry

+        public void enter() {

+            try {

+            logger.log(Level.INFO, MessageNames.FAILED_CLEAN_SHUTDOWN, 

+                    new Object[] { appEnv.getServiceName() });

+            retryCount=0;

+            /* Interrupt threads,  then start interval timer to repeat. */

+            appEnv.getWorkingContext().shutdown();

+            setTimer();

+            } catch(Throwable t) {

+                System.out.println("Got exception while entering DirtyShutdown");

+                t.printStackTrace();

+            }

+        }

+        

+        @Transition(Idle.class)

+        public void stopped() {}

+        

+        @OnExit

+        public void exit() {

+            clearTimer();

+        }

+        

+        public void timeout() {

+            appEnv.getWorkingContext().shutdown();

+            appEnv.getWorkingContext().interrupt();

+            retryCount++;

+        }

+        

+        @Guard(Failed.class)

+        public boolean isRetryCountExceeded() {

+            if (retryCount > MAX_RETRY_COUNT) {

+                logger.log(Level.INFO, MessageNames.SHUTDOWN_FAILED,

+                        new Object[] { appEnv.getServiceName()});

+            }

+            return retryCount > MAX_RETRY_COUNT;

+        }

+        

+        @Guard(Idle.class) 

+        public boolean areThreadsGone() {

+            int nThreads=appEnv.getWorkingContext().getActiveThreadCount();

+            logger.log(Level.FINE, MessageNames.N_THREADS_LEFT,

+                    new Object[]{ appEnv.getServiceName(), nThreads });

+            return nThreads==0;

+        }

+    }

+    public class Starting {

+

+        @Transition(Running.class)

+        public void startSucceeded() {

+        }

+

+        @Transition(Failed.class)

+        public void exception(Exception ex) {

+            exceptions.add(ex);

+        }

+    }

+    

+    ScheduledFuture timer=null;

+    

+    public synchronized void setTimer() {

+        Runnable command=new Runnable() {

+            public void run() {

+                eventProxy.timeout();

+                setTimer();

+            }

+        };

+        clearTimer();

+        // We're shutting down the appEnv's working context, so we need the 

+        // deployer's work manager.

+        timer=deployer.workManager.schedule(null, command, 2, TimeUnit.SECONDS);

+    }

+    

+    public synchronized void clearTimer() {

+        if (timer != null) {

+            timer.cancel(true);

+            timer=null;

+        }

+    }

+}

diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java
new file mode 100644
index 0000000..620ccd6
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.deployer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileType;
+import org.apache.river.container.ConfigurationException;
+import org.apache.river.container.Context;
+import org.apache.river.container.FileUtility;
+import org.apache.river.container.Init;
+import org.apache.river.container.Injected;
+import org.apache.river.container.InjectionStyle;
+import org.apache.river.container.MBeanRegistrar;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.Name;
+import org.apache.river.container.Utils;
+
+/**
+ *
+ * A Deployer task that deploys all the applications in a given directory when
+ * the container is started up.
+ */
+public class StartupDeployer {
+
+    private static final Logger log =
+            Logger.getLogger(StartupDeployer.class.getName(), MessageNames.BUNDLE_NAME);
+    
+    private String deployDirectory = org.apache.river.container.Strings.DEFAULT_DEPLOY_DIRECTORY;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private FileUtility fileUtility = null;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private Context context;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private StarterServiceDeployer deployer;
+    
+    @Injected(style = InjectionStyle.BY_TYPE)
+    private MBeanRegistrar mbeanRegistrar;
+    
+    @Name
+    private String myName = null;
+
+    private List<ApplicationEnvironment> applicationEnvironments =
+            new ArrayList<ApplicationEnvironment>();
+
+    public String getDeployDirectory() {
+        return deployDirectory;
+    }
+
+    public void setDeployDirectory(String deployDirectory) {
+        this.deployDirectory = deployDirectory;
+    }
+
+    FileObject deploymentDirectoryFile=null;
+    
+    @Init
+    public void init() {
+        try {
+            tryInitialize();
+        } catch (Throwable ex) {
+            log.log(Level.SEVERE, MessageNames.STARTUP_DEPLOYER_FAILED_INIT,
+                    ex);
+            throw new ConfigurationException(ex,
+                    MessageNames.STARTUP_DEPLOYER_FAILED_INIT);
+        }
+    }
+
+    private void tryInitialize() throws IOException, ParseException {
+        log.log(Level.FINE, MessageNames.STARTER_SERVICE_DEPLOYER_STARTING, myName);
+        /*
+         Establish the deployment directory.
+         */
+        deploymentDirectoryFile = fileUtility.getProfileDirectory().resolveFile(deployDirectory);
+        if (deploymentDirectoryFile == null
+                || deploymentDirectoryFile.getType() != FileType.FOLDER) {
+            log.log(Level.WARNING, MessageNames.NO_DEPLOYMENT_DIRECTORY,
+                    new Object[]{deployDirectory, fileUtility.getProfileDirectory()});
+        }
+        /*
+         Go through the deployment directory looking for services to deploy.
+         */
+        List<FileObject> serviceArchives =
+                Utils.findChildrenWithSuffix(deploymentDirectoryFile,
+                org.apache.river.container.Strings.SSAR);
+        if (serviceArchives != null) {
+            log.log(Level.FINE, MessageNames.FOUND_SERVICE_ARCHIVES,
+                    new Object[]{serviceArchives.size(), deployDirectory});
+            deployServiceArchives(serviceArchives);
+        } else {
+            log.log(Level.WARNING, MessageNames.FOUND_NO_SERVICE_ARCHIVES,
+                    new Object[]{deployDirectory});
+
+        }
+
+    }
+
+    private void deployServiceArchives(List<FileObject> serviceArchives) {
+        /*
+         Deploy those services.
+         */
+        for (FileObject archiveFile : serviceArchives) {
+            try {
+                /* Try the archive in all the deployers to see if someone can 
+                 * handle it. For now there's only one.
+                 */
+                
+                /*
+                 * Create the ApplicationEnvironment for the archive.
+                 */
+                ServiceLifeCycle deployedApp=deployer.deployServiceArchive(archiveFile);
+                // Register it as an MBean
+                registerApplication(deployedApp);
+                deployedApp.start();
+            } catch (Throwable t) {
+                log.log(Level.WARNING, MessageNames.FAILED_DEPLOY_SERVICE, archiveFile.toString());
+                log.log(Level.WARNING, MessageNames.EXCEPTION_THROWN, Utils.stackTrace(t));
+            }
+        }
+    }
+    
+    private void registerApplication(ServiceLifeCycle deployedApp) throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
+        Hashtable<String, String> props=new Hashtable<String,String>();
+        props.put(org.apache.river.container.Strings.NAME, deployedApp.getName());
+        ObjectName oName=new ObjectName(org.apache.river.container.Strings.CONTAINER_JMX_DOMAIN, props);
+        mbeanRegistrar.getMbeanServer().registerMBean(
+                deployedApp, oName);
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java
new file mode 100644
index 0000000..d9e0269
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.deployer;
+
+/**
+ *
+ * @author trasukg
+ */
+interface StatusEvents {
+    public void prepareSucceeded();
+    
+    public void startSucceeded();
+    
+    public void stopSucceeded();
+    
+    public void stopFailed();
+    
+    public void exception(Exception ex);
+    
+    public void timeout();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java b/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java
new file mode 100644
index 0000000..3f48d84
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.deployer;
+
+/**
+ *
+ * @author trasukg
+ */
+public class Strings {
+    public static final String
+            EMPTY="",
+            LPAREN="(",
+            RPAREN=")",
+            SPACE=" ";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java
new file mode 100644
index 0000000..b0c7a14
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.el;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface ArgsParser {
+
+    /**
+     * Take a command line as one string and break it into a set
+     * of arguments as expected by main(String[] args).
+     *
+     * @param input
+     * @return
+     */
+    String[] toArgs(String input);
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java
new file mode 100644
index 0000000..2e7c1fe
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.el;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ArgsParserImpl implements ArgsParser {
+    /**
+    Take a command line as one string and break it into a set
+    of arguments as expected by main(String[] args).
+    
+    @param input
+    @return 
+    */
+    @Override
+    public String[] toArgs(String input) {
+        List<String> args=new ArrayList<String>();
+        StringTokenizer tok=new StringTokenizer(input," ");
+        while(tok.hasMoreTokens()) {
+            args.add(tok.nextToken());
+        }
+        return args.toArray(new String[0]);
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java
new file mode 100644
index 0000000..4e8d546
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Controller {
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java
new file mode 100644
index 0000000..f495f51
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation type to indicate a guard method.  The annotated method must return
+ * a boolean value.  The method is run after the action events are run.  If the
+ * method returns true, the indicated transition is taken.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Guard {
+
+    /**
+     * An array of state classes that reflect the target states for this
+     * state variable.
+     *
+     * @return
+     */
+    Class[] value();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java
new file mode 100644
index 0000000..4d59888
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Annotation to indicate the initial value of a field annotated with @State.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Initial {
+    /**
+     The initial state value to be assigned to the state upon machine setup.
+     @return
+     */
+    Class value();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java
new file mode 100644
index 0000000..ede969c
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * State machine operation that invokes a method on the target instance
+ * and then queues a transition (or set of transitions).
+ */
+public class InvokeAndTransitionOperation implements Operation {
+
+    private static final Logger log=
+            Logger.getLogger(InvokeAndTransitionOperation.class.getName(),MessageNames.BUNDLE_NAME);
+    
+    
+    Method targetMethod;
+
+    /**
+     * Create an instance that executes an event method that returns a value, but
+     * does not include any transitions.
+     * @param targetMetaState
+     * @param targetMethod 
+     */
+    public InvokeAndTransitionOperation(MetaState targetMetaState, Method targetMethod) {
+        this(targetMetaState, targetMethod, new TransitionOnSubstate[0]);
+    }
+    
+    public InvokeAndTransitionOperation(MetaState targetMetaState, Method targetMethod,  TransitionOnSubstate[] transitions) {
+        this.targetMethod = targetMethod;
+        this.targetMetaState = targetMetaState;
+        this.transitions = transitions;
+    }
+    MetaState targetMetaState;
+    TransitionOnSubstate[] transitions;
+    
+    @Override
+    public void eval(StateMachineExecutor exec, Object[] args) {
+        try {
+            if (log.isLoggable(Level.FINER)) {
+                log.log(Level.FINER, MessageNames.APPLYING_EVENT_TO_STATE,
+                        new Object[]{targetMethod.getName(),
+                            targetMetaState.stateClass.getSimpleName(),
+                            targetMetaState.stateInstance});
+            }
+            exec.output(targetMethod.invoke(targetMetaState.stateInstance, args));
+        } catch(InvocationTargetException e) {
+            exec.exception(targetMetaState, targetMethod, e.getCause());
+        } catch (Exception ex) {
+            log.log(Level.SEVERE, MessageNames.APPLYING_EVENT_TO_STATE,
+                    new Object[]{targetMethod.getName(),
+                            targetMetaState.stateClass.getSimpleName(),
+                            targetMetaState.stateInstance});
+            log.log(Level.SEVERE, MessageNames.ERROR_INVOKING_TARGET_METHOD, ex);
+        }
+        // Execute the transitions
+        for (TransitionOnSubstate t:transitions) {
+            exec.queueTransition(t);
+        }
+    }
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java
new file mode 100644
index 0000000..4566f89
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * State machine operation that invokes a method on the target instance and then
+ * queues a transition (or set of transitions).
+ */
+public class InvokeGuardOperation implements Operation {
+
+    private static final Logger log =
+            Logger.getLogger(InvokeGuardOperation.class.getName(), MessageNames.BUNDLE_NAME);
+    Method targetMethod;
+
+    /**
+     * Create an instance that executes an event method that returns a value,
+     * but does not include any transitions.
+     *
+     * @param targetMetaState
+     * @param targetMethod
+     */
+    public InvokeGuardOperation(MetaState targetMetaState, Method targetMethod) {
+        this(targetMetaState, targetMethod, new TransitionOnSubstate[0]);
+    }
+
+    public InvokeGuardOperation(MetaState targetMetaState, Method targetMethod, TransitionOnSubstate[] transitions) {
+        this.targetMethod = targetMethod;
+        this.targetMetaState = targetMetaState;
+        this.transitions = transitions;
+    }
+    MetaState targetMetaState;
+    TransitionOnSubstate[] transitions;
+
+    @Override
+    public void eval(StateMachineExecutor exec, Object[] args) {
+        try {
+            if (log.isLoggable(Level.FINER)) {
+                log.log(Level.FINER, MessageNames.RUNNING_GUARD_ON_STATE,
+                        new Object[]{targetMethod.getName(),
+                            targetMetaState.stateClass.getSimpleName(),
+                            targetMetaState.stateInstance});
+            }
+            Boolean b = (Boolean) targetMethod.invoke(targetMetaState.stateInstance, args);
+            if (b.booleanValue()) {
+                // Execute the transitions
+                if (log.isLoggable(Level.FINER)) {
+                    log.log(Level.FINER, MessageNames.RUNNING_GUARD_TRANSITIONS,
+                            new Object[]{targetMethod.getName(),
+                                targetMetaState.stateClass.getSimpleName(),
+                                targetMetaState.stateInstance});
+                }
+
+                for (TransitionOnSubstate t : transitions) {
+                    exec.queueTransition(t);
+                }
+            }
+        } catch (InvocationTargetException e) {
+            exec.exception(targetMetaState, targetMethod, e.getCause());
+        } catch (Exception ex) {
+            log.log(Level.SEVERE, MessageNames.RUNNING_GUARD_ON_STATE,
+                    new Object[]{targetMethod.getName(),
+                        targetMetaState.stateClass.getSimpleName(),
+                        targetMetaState.stateInstance});
+            log.log(Level.SEVERE, MessageNames.ERROR_INVOKING_TARGET_METHOD, ex);
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java
new file mode 100644
index 0000000..cc74176
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * State machine operation that invokes a method on the target instance
+ * and then queues a transition (or set of transitions).
+ */
+public class InvokeVoidAndTransitionOperation implements Operation {
+
+    private static final Logger log=
+            Logger.getLogger(InvokeVoidAndTransitionOperation.class.getName(),MessageNames.BUNDLE_NAME);
+    
+    
+    Method targetMethod;
+
+    /**
+     * Create an instance that executes an event method that returns a value, but
+     * does not include any transitions.
+     * @param targetMetaState
+     * @param targetMethod 
+     */
+    public InvokeVoidAndTransitionOperation(MetaState targetMetaState, Method targetMethod) {
+        this(targetMetaState, targetMethod, new TransitionOnSubstate[0]);
+    }
+    
+    public InvokeVoidAndTransitionOperation(MetaState targetMetaState, Method targetMethod,  TransitionOnSubstate[] transitions) {
+        this.targetMethod = targetMethod;
+        this.targetMetaState = targetMetaState;
+        this.transitions = transitions;
+    }
+    MetaState targetMetaState;
+    TransitionOnSubstate[] transitions;
+    
+    @Override
+    public void eval(StateMachineExecutor exec, Object[] args) {
+        try {
+            if (log.isLoggable(Level.FINER)) {
+                log.log(Level.FINER, MessageNames.APPLYING_EVENT_TO_STATE,
+                        new Object[]{targetMethod.getName(),
+                            targetMetaState.stateClass.getSimpleName(),
+                            targetMetaState.stateInstance});
+            }
+            targetMethod.invoke(targetMetaState.stateInstance, args);
+        } catch(InvocationTargetException e) {
+            exec.exception(targetMetaState, targetMethod, e.getCause());
+        } catch (Exception ex) {
+            log.log(Level.SEVERE, MessageNames.APPLYING_EVENT_TO_STATE,
+                    new Object[]{targetMethod.getName(),
+                            targetMetaState.stateClass.getSimpleName(),
+                            targetMetaState.stateInstance});
+            log.log(Level.SEVERE, MessageNames.ERROR_INVOKING_TARGET_METHOD, ex);
+        }
+        // Execute the transitions
+        for (TransitionOnSubstate t:transitions) {
+            exec.queueTransition(t);
+        }
+    }
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java b/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java
new file mode 100644
index 0000000..bbc82c7
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+/**
+ * Message names for the state machine implementation's logging.
+ */
+public class MessageNames {
+
+    public static final String BUNDLE_NAME = "org.apache.river.container.hsm.Messages";
+    public static final String APPLYING_EVENT_TO_STATE = "applyingEventToState",
+            BEGINNING_COMPILE="beginningCompile",
+            CANT_RESOLVE_TRANSITIONS_FOR_CLASS = "cantResolveTransitionsForClass",
+            COMPILE_COMPLETED="compileCompleted",
+            ENTRY_METHOD_ISNT_VOID = "entryMethodIsntVoid",
+            ERROR_APPLYING_TRANSITION="errorApplyingTransition",
+            ERROR_CREATING_PROXY="errorCreatingProxy",
+            ERROR_INSTANTIATING="errorInstantiating",
+            ERROR_INVOKING_TARGET_METHOD = "errorInvokingTargetMethod",
+            EXIT_METHOD_ISNT_VOID = "exitMethodIsntVoid",
+            GUARD_METHOD_DOESNT_RETURN_BOOLEAN = "guardMethodDoesntReturnBoolean",
+            METASTATE_WAS_INSTANTIATED="metaStateWasInstantiated",
+            MULTIPLE_EXCEPTIONS_THROWN="multipleExceptionsThrown",
+            NO_PARENT_INSTANCE="noParentInstance",
+            QUEUED_TRANSITION="queuedTransition",
+            RUNNING_GUARD_ON_STATE = "runningGuardOnState",
+            RUNNING_GUARD_TRANSITIONS="runningGuardTransitions",
+            SETTING_INITIAL_SUBSTATE="settingInitialSubstate",
+            SETTING_FIELD_TO="settingFieldTo",
+            STORING_EXCEPTION="storingException";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java
new file mode 100644
index 0000000..4d8eb6e
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An object that corresponds to a state instance.
+ */
+public class MetaState {
+    
+    Object stateInstance=null;
+    Class stateClass=null;
+    MetaState parent=null;
+    
+    public List<MetaState> getActiveMetaStates() {
+        List<MetaState> activeStates=new ArrayList<MetaState>();
+        getActiveMetaStates(activeStates);
+        return activeStates;
+    }
+    
+    public void getActiveMetaStates(List<MetaState> activeStates) {
+        /* Add my stateInstance's class. */
+        activeStates.add(this);
+        /* Iterate down for each substate. */
+        for(SubstateInfo substate: substates) {
+            substate.getActiveMetaState().getActiveMetaStates(activeStates);
+        }
+    }
+    
+    public List<Class> getActiveStates() {
+        List<MetaState> activeStates=getActiveMetaStates();
+        List<Class> activeStateClasses=new ArrayList(activeStates.size());
+        for(MetaState ms: activeStates) {
+            activeStateClasses.add(ms.stateClass);
+        }
+        return activeStateClasses;
+    }
+    List<SubstateInfo> substates=new ArrayList<SubstateInfo>();
+    
+    Map<Method, Operation> eventMethods=new HashMap<Method, Operation>();
+    
+    public void visitAll(MetaStateVisitor visitor) {
+        visitor.visit(this);
+        for (SubstateInfo substateInfo: substates) {
+            for (MetaState ms: substateInfo.getPossibleMetaStates()) {
+                ms.visitAll(visitor);
+            }
+        }
+    }
+    
+    List<Operation> guardMethods=new ArrayList<Operation>();
+
+    List<Operation> entryMethods=new ArrayList<Operation>();
+    
+    List<Operation> exitMethods=new ArrayList<Operation>();
+    
+    List<TransitionOnSubstate> entryTransitions=new ArrayList<TransitionOnSubstate>();
+    
+    public String toString() {
+        return stateClass==null?"Uninitialized metastate":("Meta-" + stateClass.getName());
+    }
+    
+    /**
+     * Return a string representation of the possible state structure.  Mainly for unit-testing.
+     * @return 
+     */
+    public String getStateStructure() {
+        StringBuilder sb=new StringBuilder();
+        sb.append(stateClass.getSimpleName());
+        if (substates.size() != 0) {
+            sb.append("(");
+            for (SubstateInfo si:substates) {
+                sb.append(si.getField().getName());
+                sb.append("(");
+                for (MetaState ms: si.getPossibleMetaStates()) {
+                    sb.append(ms.getStateStructure());
+                    sb.append(" ");
+                }
+                sb.append(") ");
+            }
+            sb.append(") ");
+        }
+        return sb.toString();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java
new file mode 100644
index 0000000..9fbda12
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java
@@ -0,0 +1,13 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.river.container.hsm;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface MetaStateVisitor {
+    public void visit(MetaState metaState);
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java b/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java
new file mode 100644
index 0000000..179b2fb
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Indicates that the annotated method should be invoked whenever the state is
+ entered.  There is no guarantee on the order of execution of @OnEntry methods,
+ only that the exit methods are invoked before the entry methods for the next
+ state.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnEntry {
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java b/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java
new file mode 100644
index 0000000..7a3de76
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Indicates that the annotated method should be invoked whenever the state is
+ exited.  There is no guarantee on the order of execution of @OnExit methods,
+ only that the exit methods are invoked before the entry methods for the next
+ state.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnExit {
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java
new file mode 100644
index 0000000..ece298e
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+/**
+ Interface for an object that evaluates "something" against a set of arguments.
+ * @author trasukg
+ */
+interface Operation {
+
+    /**
+     Run the operation against a set of arguments.
+     @param args
+     @return
+     */
+    void eval(StateMachineExecutor exec, Object[] args);
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java b/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
new file mode 100644
index 0000000..971ca6e
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
@@ -0,0 +1,391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ */
+public class PlainStateMachineExecutor implements StateMachineExecutor, StateMachineInfo {
+
+    private static final Logger log =
+            Logger.getLogger(PlainStateMachineExecutor.class.getName(),
+            MessageNames.BUNDLE_NAME);
+
+    /**
+     * Convenience method to compile a state machine and instantiate it.
+     *
+     * @param rootStateClass
+     * @return
+     */
+    public static Object createProxy(Class rootStateClass) {
+        StateMachineCompiler compiler = new StateMachineCompiler();
+        try {
+            MetaState rootState = compiler.compile(rootStateClass);
+            instantiate(rootState);
+            return createProxy(rootState);
+        } catch (StateMachineException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_CREATING_PROXY);
+        }
+    }
+
+    /**
+     * Convenience method to compile a state machine and instantiate it.
+     *
+     * @param rootStateClass
+     * @return
+     */
+    public static Object createProxy(Object rootStateInstance) {
+        StateMachineCompiler compiler = new StateMachineCompiler();
+        try {
+            MetaState rootState = compiler.compile(rootStateInstance.getClass());
+            rootState.stateInstance = rootStateInstance;
+            instantiate(rootState);
+            return createProxy(rootState);
+        } catch (StateMachineException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_CREATING_PROXY);
+        }
+    }
+
+    /**
+     * Create a fully-instantiated metastate from a compiled metastate.
+     *
+     * @param rootState
+     */
+    public static void instantiate(MetaState rootState) {
+        /* Create a user class instance to go with every metastate. */
+        rootState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                try {
+                    if (metaState.stateInstance != null) {
+                        return;
+                    }
+                    /*
+                     * Goal here is to create a stateInstance for each metastate.
+                     * In the simple case, the stateClass is a root class, and we can
+                     * just instantiate it.
+                     */
+                    if (metaState.stateClass.getEnclosingClass() == null) {
+                        metaState.stateInstance = metaState.stateClass.newInstance();
+                    } else {
+                        /* OK, we need to get an instance of the enclosing class.
+                         * That should be the stateInstance of a parent state.
+                         */
+                        Object parentInstance = findAParentInstance(metaState, metaState.stateClass.getEnclosingClass());
+
+                        Constructor con = metaState.stateClass.getConstructor(parentInstance.getClass());
+                        metaState.stateInstance = con.newInstance(parentInstance);
+                    }
+                    log.log(Level.FINE, MessageNames.METASTATE_WAS_INSTANTIATED, new Object[]{metaState.stateClass, metaState.stateInstance});
+                } catch (Exception ex) {
+                    throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_INSTANTIATING);
+                }
+            }
+        });
+        /* Now set all the initial state instance values. */
+        rootState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                try {
+                    for (SubstateInfo ssi : metaState.substates) {
+                        ssi.setObjectThatHoldsField(metaState.stateInstance);
+                        log.log(Level.FINE, MessageNames.SETTING_FIELD_TO, new Object[]{ssi.getField().getName(), metaState.stateInstance, ssi.getInitialMetaState().stateInstance});
+                        writeStateField(ssi, metaState);
+                    }
+                } catch (Exception ex) {
+                    throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_INSTANTIATING);
+                }
+            }
+        });
+    }
+
+    private static void writeStateField(SubstateInfo ssi, MetaState metaState) throws IllegalArgumentException, IllegalAccessException, SecurityException {
+        boolean originalAccess = ssi.getField().isAccessible();
+        ssi.getField().setAccessible(true);
+        ssi.getField().set(ssi.getObjectThatHoldsField(), metaState.stateInstance);
+        ssi.getField().setAccessible(originalAccess);
+    }
+
+    private static Object findAParentInstance(MetaState metaState, Class enclosingClass) {
+        for (MetaState currentState = metaState.parent; currentState != null; currentState = currentState.parent) {
+            if (currentState.stateClass == enclosingClass) {
+                return currentState.stateInstance;
+            }
+        }
+        throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.NO_PARENT_INSTANCE,
+                new Object[]{enclosingClass, metaState.stateInstance});
+    }
+
+    public static Object createProxy(MetaState instantiatedMetaState) {
+        RootState rootStateAnnotation = (RootState) instantiatedMetaState.stateClass.getAnnotation(RootState.class);
+        Class[] eventInterfaces = rootStateAnnotation.value();
+        PlainStateMachineExecutor executor = new PlainStateMachineExecutor(instantiatedMetaState);
+        executor.activate();
+        Object proxy =
+                Proxy.newProxyInstance(eventInterfaces[0].getClassLoader(),
+                eventInterfaces,
+                executor.getInvocationHandler());
+        return proxy;
+    }
+    InvocationHandler invocationHandler = null;
+
+    public InvocationHandler getInvocationHandler() {
+        if (invocationHandler == null) {
+            invocationHandler = new InvocationHandler() {
+                @Override
+                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                    return runEvent(method, args);
+                }
+            };
+        }
+        return invocationHandler;
+    }
+
+    PlainStateMachineExecutor(MetaState instantiatedMetaState) {
+        this.rootMetaState = instantiatedMetaState;
+        fillInControllerReferences();
+        fillInRootStateReferences();
+    }
+    MetaState rootMetaState = null;
+
+    private void activate() {
+        List<MetaState> initialStates = buildActiveStates();
+        runEntryMethods(initialStates);
+    }
+
+    private synchronized Object runEvent(Method eventMethod, Object[] args)
+            throws Throwable {
+        clearOutput();
+        clearExceptions();
+        clearTransitions();
+        List<MetaState> initialStates = buildActiveStates();
+        runEventActions(initialStates, eventMethod, args);
+        runGuardMethods(initialStates);
+        applyTransitions(initialStates);
+        List<MetaState> finalStates = buildActiveStates();
+        List<MetaState> exiting = new ArrayList<MetaState>();
+        List<MetaState> entering = new ArrayList<MetaState>();
+        calculateStateDelta(initialStates, finalStates, exiting, entering);
+        runExitMethods(exiting);
+        runEntryMethods(entering);
+        if (hasExceptions()) {
+            throw buildOutputException();
+        }
+        if (eventMethod.getReturnType() != null) {
+            return buildOutputValue(eventMethod.getReturnType());
+        }
+        return null;
+    }
+    Object output = null;
+
+    private void clearOutput() {
+        output = null;
+    }
+
+    private Object buildOutputValue(Class returnType) {
+        return output;
+    }
+    List<Throwable> exceptions = new ArrayList<Throwable>();
+
+    private void clearExceptions() {
+        exceptions.clear();
+    }
+
+    private boolean hasExceptions() {
+        return !exceptions.isEmpty();
+    }
+
+    private Throwable buildOutputException() {
+        if (exceptions.size() == 1) {
+            return exceptions.get(0);
+        } else {
+            return new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.MULTIPLE_EXCEPTIONS_THROWN,
+                    new Object[0]);
+        }
+    }
+    List<TransitionOnSubstate> queuedTransitions = new ArrayList<TransitionOnSubstate>();
+
+    private void clearTransitions() {
+        queuedTransitions.clear();
+    }
+
+    @Override
+    public void queueTransition(TransitionOnSubstate t) {
+        log.log(Level.FINER, MessageNames.QUEUED_TRANSITION,
+                new Object[]{t.targetMetaState.stateClass.getSimpleName(),
+                    t.targetMetaState.parent.stateClass.getSimpleName()});
+        queuedTransitions.add(t);
+    }
+
+    @Override
+    public void output(Object outputObject) {
+        output = outputObject;
+    }
+
+    @Override
+    public void exception(MetaState metaState, Method interfaceMethod, Throwable cause) {
+        log.log(Level.FINE, MessageNames.STORING_EXCEPTION, new Object[]{metaState.stateInstance, interfaceMethod.getName(), cause.toString()});
+        exceptions.add(cause);
+    }
+
+    private List<MetaState> buildActiveStates() {
+        return rootMetaState.getActiveMetaStates();
+    }
+
+    private void runEventActions(List<MetaState> activeStates, Method eventInterfaceMethod, Object[] args) {
+        boolean thereWasAnEventMethod=false;
+        for (MetaState ms : activeStates) {
+            Operation op = ms.eventMethods.get(eventInterfaceMethod);
+            if (op != null) {
+                thereWasAnEventMethod=true;
+                op.eval(this, args);
+            }
+        }
+        if(!thereWasAnEventMethod) {
+            exceptions.add(new IllegalStateException());
+        }
+    }
+
+    private void runGuardMethods(List<MetaState> activeStates) {
+        for (MetaState ms : activeStates) {
+            for (Operation op : ms.guardMethods) {
+                op.eval(this, null);
+            }
+        }
+    }
+
+    private void runExitMethods(List<MetaState> exitingStates) {
+        for (MetaState ms : exitingStates) {
+            for (Operation op : ms.exitMethods) {
+                op.eval(this, null);
+            }
+        }
+    }
+
+    private void runEntryMethods(List<MetaState> enteringStates) {
+        for (MetaState ms : enteringStates) {
+            for (Operation op : ms.entryMethods) {
+                op.eval(this, null);
+            }
+        }
+    }
+
+    private void applyTransitions(List<MetaState> activeStates) {
+
+        while (!queuedTransitions.isEmpty()) {
+            /* Pull a transition. */
+            TransitionOnSubstate tos = queuedTransitions.remove(queuedTransitions.size() - 1);
+            /* Apply it. */
+            applyTransition(tos);
+            /* Add any implied transitions to the list of transitions, if the new state is not 
+             * currently active. */
+            if (!activeStates.contains(tos.targetMetaState)) {
+                queuedTransitions.addAll(tos.targetMetaState.entryTransitions);
+            }
+        }
+    }
+
+    private void applyTransition(TransitionOnSubstate tos) {
+        try {
+            tos.substate.setActiveMetaState(tos.targetMetaState);
+            writeStateField(tos.substate, tos.targetMetaState);
+            /* Get rid of this - it doesn't work on private fields.  Use the writeStateField() above.
+             tos.substate.getField().set(tos.targetMetaState.parent.stateInstance, tos.targetMetaState.stateInstance);
+             */
+        } catch (Exception ex) {
+            StateMachineException sme = new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_APPLYING_TRANSITION,
+                    new Object[]{ex.getMessage()});
+            //sme.printStackTrace();
+            throw sme;
+        }
+    }
+
+    private void calculateStateDelta(List<MetaState> initialStates, List<MetaState> finalStates, List<MetaState> exiting, List<MetaState> entering) {
+        for (MetaState initialState : initialStates) {
+            if (!finalStates.contains(initialState)) {
+                exiting.add(initialState);
+            }
+        }
+        for (MetaState finalState : finalStates) {
+            if (!initialStates.contains(finalState)) {
+                entering.add(finalState);
+            }
+        }
+    }
+
+    private void fillInControllerReferences() {
+        rootMetaState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                log.fine("Visiting " + metaState + " to fill in controller reference.");
+                fillInReferenceFields(metaState, Controller.class, PlainStateMachineExecutor.this);
+            }
+        });
+    }
+
+    private void fillInRootStateReferences() {
+        rootMetaState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                log.fine("Visiting " + metaState + " to fill in root state reference.");
+                fillInReferenceFields(metaState, RootState.class, rootMetaState.stateInstance);
+            }
+        });
+    }
+
+    private void fillInReferenceFields(MetaState metaState, Class<? extends Annotation> aClass, Object value) {
+
+        for (Field f : metaState.stateClass.getFields()) {
+
+            if (f.getAnnotation(aClass) != null) {
+                try {
+                    log.fine("Setting field " + metaState.stateInstance + "." + f.getName()
+                            + " to " + value);
+                    boolean accessible = f.isAccessible();
+                    f.setAccessible(true);
+                    f.set(metaState.stateInstance, value);
+                    f.setAccessible(accessible);
+                } catch (Exception ex) {
+                    throw new StateMachineException(MessageNames.BUNDLE_NAME,
+                            MessageNames.ERROR_INSTANTIATING, new Object[]{ex.getMessage()});
+
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<Class> getActiveStates() {
+        return rootMetaState.getActiveStates();
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java
new file mode 100644
index 0000000..6f30ed0
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Annotation that indicates a state should be retained across transitions,
+ rather than reset every time the parent state is entered.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Retained {
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java b/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java
new file mode 100644
index 0000000..415fbb1
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate the root class of a hierarchical state machine.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface RootState {
+    /**
+     The event interfaces that this machine is intended to implement.
+     @return
+     */
+    Class[] value();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/State.java b/river-container-core/src/main/java/org/apache/river/container/hsm/State.java
new file mode 100644
index 0000000..01d258b
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/State.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Annotation type to indicate a variable that stores a state in a
+ hierarchical state machine.  To specify parallel state (AND-States as per
+ Harel), simply include more than one field marked with @State.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface State {
+    /**
+     An array of state classes that reflect the allowable states for this
+     state variable.
+     @return
+     */
+    Class[] value();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
new file mode 100644
index 0000000..8597424
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.river.container.Utils;
+
+/**
+ * Compiler for state machine instances. The "input" to the compiler is actually
+ * a class that is annotated with the
+ *
+ * @RootState annotation.
+ *
+ */
+public class StateMachineCompiler {
+
+    private static final Logger log =
+            Logger.getLogger(StateMachineCompiler.class.getName(), MessageNames.BUNDLE_NAME);
+    Class[] eventInterfaces = null;
+
+    /**
+     * Retrieve the event interface, as set by the
+     *
+     * @RootState annotation.
+     * @return The event interface.
+     */
+    public Class[] getEventInterfaces() {
+        return eventInterfaces;
+    }
+
+    public MetaState compile(Class rootStateClass) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+        log.log(Level.FINE, MessageNames.BEGINNING_COMPILE, new Object[]{rootStateClass.getName()});
+        findEventInterfaces(rootStateClass);
+        // First pass: create all metastates
+        MetaState rootMetaState = createMetaState(rootStateClass);
+        // Second pass: Fill in event methods
+        rootMetaState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState ms) {
+                try {
+                    fillInEventMethods(ms);
+                    fillInGuardMethods(ms);
+                    fillInEntryMethods(ms);
+                    fillInExitMethods(ms);
+                } catch (Exception ex) {
+                    throw new RuntimeException(ex);
+                }
+            }
+        });
+        log.log(Level.FINE, MessageNames.COMPILE_COMPLETED, new Object[]{rootStateClass.getName()});
+        return rootMetaState;
+    }
+
+    private void findEventInterfaces(Class rootStateClass) {
+        RootState rootStateAnnotation = (RootState) rootStateClass.getAnnotation(RootState.class);
+        if (rootStateAnnotation == null || rootStateAnnotation.value() == null) {
+            throw new RuntimeException("Root state class must specify @RootState(interfaceClass).");
+        }
+        eventInterfaces = rootStateAnnotation.value();
+    }
+
+    MetaState createMetaState(Class stateClass) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+        return createMetaState(null, stateClass);
+    }
+
+    MetaState createMetaState(MetaState parentMetaState, Class stateClass) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
+
+        MetaState metaState = new MetaState();
+        metaState.stateClass = stateClass;
+        metaState.parent = parentMetaState;
+
+        processSubstates(metaState);
+        return metaState;
+    }
+
+    /**
+     * Look for fields that are annotated with
+     *
+     * @State and fill in the required substate info.
+     * @param metaState
+     */
+    private void processSubstates(MetaState metaState) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
+        for (Field f : metaState.stateClass.getDeclaredFields()) {
+            // Look for fields annotated with @State.
+            State ann = (State) f.getAnnotation(State.class);
+            if (ann == null) {
+                continue;
+            }
+            SubstateInfo info = new SubstateInfo();
+            info.setField(f);
+            metaState.substates.add(info);
+            if (ann.value() == null) {
+                throw new RuntimeException("@State needs a list of possible states");
+            }
+            info.setPossibleMetaStates(createPossibleMetaStates(metaState, ann.value()).toArray(new MetaState[0]));
+            Initial initialAnn = f.getAnnotation(Initial.class);
+            if (initialAnn == null) {
+                throw new RuntimeException("Need initial state for "
+                        + f.getName());
+            }
+            MetaState initialMetaState = findMetaState(info.getPossibleMetaStates(), initialAnn.value());
+            if (initialMetaState == null) {
+                throw new RuntimeException("Couldn't find a metastate that corresponds to " + initialAnn.value() + " in " + Utils.format(info.getPossibleMetaStates()));
+            }
+            info.setInitialMetaState(initialMetaState);
+
+            /* While we're at it, set the active metastate. */
+            info.setActiveMetaState(info.getInitialMetaState());
+            Retained retainedAnn = f.getAnnotation(Retained.class);
+            info.setRetained(retainedAnn != null);
+
+            /* Add the non-retained metastate to the list of transitions-on-entry of the
+             * parent metastat.
+             */
+            if (!info.isRetained()) {
+                metaState.entryTransitions.add(new TransitionOnSubstate(info, info.getInitialMetaState()));
+            }
+        }
+    }
+
+    private List<MetaState> createPossibleMetaStates(MetaState metaState, Class[] substateClasses) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
+        List<MetaState> metaStates = new ArrayList<MetaState>();
+        /* For each class, we'll need an instance. */
+        for (Class substateClass : substateClasses) {
+            metaStates.add(createMetaState(metaState, substateClass));
+        }
+        return metaStates;
+
+    }
+
+    /**
+     * Have a feeling this will end up in the StateMachineRunner
+     */
+    private Object findInstance(MetaState parentMetaState, Class enclosingClass) {
+        MetaState currentState = parentMetaState;
+        while (currentState != null) {
+            if (currentState.stateClass == enclosingClass) {
+                return currentState.stateInstance;
+            }
+            currentState = currentState.parent;
+        }
+        return null;
+    }
+
+    private MetaState findMetaState(MetaState[] possibleMetaStates, Class value) {
+        for (MetaState metaState : possibleMetaStates) {
+            if (metaState.stateClass == value) {
+                return metaState;
+            }
+        }
+        return null;
+    }
+
+    private void fillInEventMethods(MetaState metaState) throws NoSuchMethodException {
+        for (Class eventInterface : getEventInterfaces()) {
+            for (Method m : eventInterface.getMethods()) {
+                /* See if the state class has a method with the same name and 
+                 * parameters.
+                 */
+                Method eventMethod = null;
+                try {
+                    eventMethod = metaState.stateClass.getMethod(m.getName(), m.getParameterTypes());
+                } catch (NoSuchMethodException nsme) {
+                    // Silent catch - lets the event method remain null, which we check for.
+                }
+                Operation operation = null;
+                if (eventMethod != null) {
+                    if (eventMethod.getReturnType() != null && !m.getReturnType().isAssignableFrom(eventMethod.getReturnType())) {
+                        throw new RuntimeException("If the event method returns a value, its type must match the event interface's method.  Required"
+                                + m.getReturnType() + ", found " + eventMethod.getReturnType());
+                    }
+                    Transition transition = eventMethod.getAnnotation(Transition.class);
+                    // Fill in from here down!
+                    // Return value, no transition
+                    if (eventMethod.getReturnType() != null && transition == null) {
+                        operation = new InvokeAndTransitionOperation(metaState, eventMethod);
+                    } // Return value with transition
+                    else if (eventMethod.getReturnType() != null && transition != null) {
+                        TransitionOnSubstate[] transitions = resolveTransitions(metaState, transition.value());
+                        operation = new InvokeAndTransitionOperation(metaState, eventMethod, transitions);
+                    } // No return value, no transition
+                    else if (eventMethod.getReturnType() == null && transition == null) {
+                        operation = new InvokeVoidAndTransitionOperation(metaState, eventMethod);
+                    } // No return value, with transition
+                    else if (eventMethod.getReturnType() == null && transition != null) {
+                        TransitionOnSubstate[] transitions = resolveTransitions(metaState, transition.value());
+                        operation = new InvokeVoidAndTransitionOperation(metaState, eventMethod, transitions);
+                    }
+                }
+                metaState.eventMethods.put(m, operation);
+            }
+        }
+    }
+
+    private void fillInGuardMethods(MetaState metaState) {
+        Class stateClass = metaState.stateClass;
+        for (Method m : stateClass.getMethods()) {
+            Guard guard = m.getAnnotation(Guard.class);
+            if (guard != null) {
+                if (m.getReturnType() != boolean.class) {
+                    throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.GUARD_METHOD_DOESNT_RETURN_BOOLEAN,
+                            new Object[]{m.toGenericString(), m.getReturnType()});
+                }
+                TransitionOnSubstate[] transitions = resolveTransitions(metaState, guard.value());
+                Operation op = new InvokeGuardOperation(metaState, m, transitions);
+                metaState.guardMethods.add(op);
+            }
+
+        }
+    }
+
+    private void fillInEntryMethods(MetaState metaState) {
+        Class stateClass = metaState.stateClass;
+        for (Method m : stateClass.getMethods()) {
+            OnEntry onEntry = m.getAnnotation(OnEntry.class);
+            if (onEntry != null) {
+                if (m.getReturnType() != void.class) {
+                    throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.ENTRY_METHOD_ISNT_VOID,
+                            new Object[]{m.toGenericString(), m.getReturnType()});
+                }
+                Operation op = new InvokeVoidAndTransitionOperation(metaState, m, new TransitionOnSubstate[0]);
+                metaState.entryMethods.add(op);
+            }
+
+        }
+    }
+
+    private void fillInExitMethods(MetaState metaState) {
+        Class stateClass = metaState.stateClass;
+        for (Method m : stateClass.getMethods()) {
+            OnExit onEntry = m.getAnnotation(OnExit.class);
+            if (onEntry != null) {
+                if (m.getReturnType() != void.class) {
+                    throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.EXIT_METHOD_ISNT_VOID,
+                            new Object[]{m.toGenericString(), m.getReturnType()});
+                }
+                Operation op = new InvokeVoidAndTransitionOperation(metaState, m, new TransitionOnSubstate[0]);
+                metaState.exitMethods.add(op);
+            }
+
+        }
+    }
+
+    /**
+     * Convert an array of classes to a set of transitions with reference to a
+     * given metastate. Transitions are specified as an array of classes on an
+     * event method in a state class. In order to be used, they need to be
+     * resolved to a particular substate field related to that metastate. <br>
+     * The class referenced can be one of <ul> <li>a class that is a possible
+     * state of one of the substates defined on the state class that contains
+     * the annotated event method.</li> <li>A class that is a possible peer
+     * state to the state class that contains the annotated method. </li> <li>A
+     * class that is a possible peer state to an ancester of the state class
+     * that contains the annotated method. </li> </ul>
+     *
+     * @param metaState
+     * @param transitionClasses
+     * @return
+     */
+    private TransitionOnSubstate[] resolveTransitions(MetaState metaState, Class[] transitionClasses) {
+        List<TransitionOnSubstate> allTransitions = new ArrayList<TransitionOnSubstate>();
+        for (Class c : transitionClasses) {
+            List<TransitionOnSubstate> transitionsForClass = new ArrayList<TransitionOnSubstate>();
+            resolveSubstateTransitions(transitionsForClass, metaState, c);
+            resolvePeerAndParentTransitions(transitionsForClass, metaState, c);
+            if (transitionsForClass.isEmpty()) {
+                throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.CANT_RESOLVE_TRANSITIONS_FOR_CLASS,
+                        new Object[]{metaState.stateClass.getName(), c.getName()});
+            }
+            allTransitions.addAll(transitionsForClass);
+        }
+        return allTransitions.toArray(new TransitionOnSubstate[0]);
+    }
+
+    /**
+     * Go through each of the substates and find any metastates that correspond
+     * to the given state class. Create a transition for them and add it to the
+     * list of transitions.
+     *
+     * @param transitionsForClass
+     * @param metaState
+     * @param c
+     */
+    private void resolveSubstateTransitions(List<TransitionOnSubstate> transitionsForClass, MetaState metaState, Class c) {
+        for (SubstateInfo substateInfo : metaState.substates) {
+            for (MetaState m : substateInfo.getPossibleMetaStates()) {
+                if (m.stateClass == c) {
+                    transitionsForClass.add(new TransitionOnSubstate(substateInfo, m));
+                }
+            }
+        }
+    }
+
+    private void resolvePeerAndParentTransitions(List<TransitionOnSubstate> transitionsForClass, MetaState metaState, Class c) {
+        for (MetaState currentMetaState = metaState.parent; currentMetaState != null; currentMetaState = currentMetaState.parent) {
+            resolveSubstateTransitions(transitionsForClass, currentMetaState, c);
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java
new file mode 100644
index 0000000..85b04b9
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * This is a runtime exception with localized error messages.
+ */
+public class StateMachineException extends RuntimeException {
+    private String messageBundleName;
+    private String messageKey;
+
+    public String getMessageBundleName() {
+        return messageBundleName;
+    }
+
+    public String getMessageKey() {
+        return messageKey;
+    }
+
+    public Object[] getMessageParameters() {
+        return messageParameters;
+    }
+    private Object[] messageParameters;
+
+    /**
+     Construct a runtime exception with a localized message.
+     @param messageBundleName
+     @param messageKey
+     @param messageParameters
+     */
+    public StateMachineException(String messageBundleName,
+            String messageKey,
+            Object[] messageParameters) {
+        this.messageBundleName=messageBundleName;
+        this.messageKey=messageKey;
+        this.messageParameters=messageParameters;
+    }
+
+    /**
+     Construct a runtime exception with a localized message.
+     @param messageBundleName
+     @param messageKey
+     @param messageParameters
+     */
+    public StateMachineException(Throwable rootCause,
+            String messageBundleName,
+            String messageKey,
+            Object ... messageParameters) {
+        super(rootCause);
+        this.messageBundleName=messageBundleName;
+        this.messageKey=messageKey;
+        this.messageParameters=messageParameters;
+    }
+
+    /**
+     Localize and return the error message, according to the default Locale.
+     Note that the resolution of the resource bundle and the localization is
+     deferred until this method is called, allowing the existence of different
+     resource bundles or locales in the thrower and receiver (e.g. in the case
+     of a serialized exception passed between two JVMs.
+
+     @return The localized message.
+     */
+    @Override
+    public String getMessage() {
+        ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName);
+        String message=(String) bundle.getObject(messageKey);
+        return MessageFormat.format(message, messageParameters);
+    }
+
+    /**
+     Return the message localized using the given locale.
+     Note that the resolution of the resource bundle and the localization is
+     deferred until this method is called, allowing the existence of different
+     resource bundles or locales in the thrower and receiver (e.g. in the case
+     of a serialized exception passed between two JVMs.
+
+     @param locale
+     @return
+     */
+    public String getMessage(Locale locale) {
+        ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName, locale);
+        String message=(String) bundle.getObject(messageKey);
+        return MessageFormat.format(message, messageParameters);
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java
new file mode 100644
index 0000000..61fae76
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ */
+public interface StateMachineExecutor {
+    
+    /**
+     * Queue a transition to a new state, which will be executed after the 
+     * current action.
+     * @param parentState
+     * @param newState 
+     */
+    public void queueTransition(TransitionOnSubstate t);
+
+    void output(Object outputObject);
+
+    void exception(MetaState metaState, Method interfaceEvent, Throwable cause);
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java
new file mode 100644
index 0000000..2571c3a
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java
@@ -0,0 +1,15 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.river.container.hsm;
+
+import java.util.List;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface StateMachineInfo {
+    public List<Class> getActiveStates();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/SubstateInfo.java b/river-container-core/src/main/java/org/apache/river/container/hsm/SubstateInfo.java
new file mode 100644
index 0000000..df6a76a
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/SubstateInfo.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.Field;
+
+/**
+ *
+ * Information on substates within a MetaState.
+ */
+class SubstateInfo {
+
+    private Field field;
+    private Object objectThatHoldsField;
+
+    public Object getObjectThatHoldsField() {
+        return objectThatHoldsField;
+    }
+
+    public void setObjectThatHoldsField(Object objectThatHoldsField) {
+        this.objectThatHoldsField = objectThatHoldsField;
+    }
+    
+    /** this field will disappear when the StateMachineCompiler is complete. */
+    private Class[] possibleStates;
+    
+    private MetaState[] possibleMetaStates;
+
+    public MetaState[] getPossibleMetaStates() {
+        return possibleMetaStates;
+    }
+
+    public void setPossibleMetaStates(MetaState[] possibleMetaStates) {
+        this.possibleMetaStates = possibleMetaStates;
+    }
+
+    public MetaState getInitialMetaState() {
+        return initialMetaState;
+    }
+
+    public void setInitialMetaState(MetaState initialMetaState) {
+        this.initialMetaState = initialMetaState;
+    }
+    
+    /** this field will disappear when the StateMachineCompiler is complete. */
+    private Class initialState;
+    
+    private MetaState initialMetaState;
+    
+    private boolean retained=false;
+    private MetaState activeMetaState;
+
+    public MetaState getActiveMetaState() {
+        return activeMetaState;
+    }
+
+    public void setActiveMetaState(MetaState activeMetaState) {
+        this.activeMetaState = activeMetaState;
+    }
+    
+    public Field getField() {
+        return field;
+    }
+
+    public void setField(Field field) {
+        this.field = field;
+    }
+
+    public Class getInitialState() {
+        return initialState;
+    }
+
+    public void setInitialState(Class initialState) {
+        this.initialState = initialState;
+    }
+
+    public Class[] getPossibleStates() {
+        return possibleStates;
+    }
+
+    public void setPossibleStates(Class[] possibleStates) {
+        this.possibleStates = possibleStates;
+    }
+
+    public boolean isRetained() {
+        return retained;
+    }
+
+    public void setRetained(boolean retained) {
+        this.retained = retained;
+    }
+
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Transition.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Transition.java
new file mode 100644
index 0000000..75348f4
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Transition.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation type to indicate an unconditional  transition that is applied after an
+ * event method is executed.  
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Transition {
+
+    /**
+     * An array of state classes that reflect the target states for this
+     * state variable.
+     *
+     * @return
+     */
+    Class[] value();
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java b/river-container-core/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java
new file mode 100644
index 0000000..4c416ce
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java
@@ -0,0 +1,40 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.river.container.hsm;
+
+/**
+ * Holds a transition on a substate.
+ */
+public class TransitionOnSubstate {
+    
+    // package access, so the executor can use it directly.
+    SubstateInfo substate;
+
+    /**
+     * Create an instance specifying the target metastate for the given
+     * substate.
+     * 
+     * @param substate
+     * @param targetMetaState 
+     */
+    public TransitionOnSubstate(SubstateInfo substate, MetaState targetMetaState) {
+        this.substate = substate;
+        this.targetMetaState = targetMetaState;
+    }
+
+    public SubstateInfo getSubstate() {
+        return substate;
+    }
+
+
+    public MetaState getTargetMetaState() {
+        return targetMetaState;
+    }
+    
+    // package access, so the executor can use it directly.
+    MetaState targetMetaState;
+    
+    
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/liaison/Strings.java b/river-container-core/src/main/java/org/apache/river/container/liaison/Strings.java
new file mode 100644
index 0000000..e61864d
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/liaison/Strings.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.liaison;
+
+/**
+ *
+ Strings used in the liaison to the container.  This is a separate class
+ from org.apache.container.Strings even though there are duplicates, so that
+ we can pull org.apache.river.container.liaison into its own jar and include
+ that standalone into the contained application's classpath.
+ * @author trasukg
+ */
+public class Strings {
+    public static final String
+            DASH="-",
+            ERROR_CLOSING_FILE="Error Closing File",
+            JAR="jar";
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/liaison/VirtualFileSystemConfiguration.java b/river-container-core/src/main/java/org/apache/river/container/liaison/VirtualFileSystemConfiguration.java
new file mode 100644
index 0000000..b99a8c5
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/liaison/VirtualFileSystemConfiguration.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.liaison;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.config.Configuration;
+import net.jini.config.ConfigurationException;
+import net.jini.config.ConfigurationFile;
+import net.jini.config.ConfigurationNotFoundException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.VFS;
+
+/**
+ *
+ * @author trasukg
+ */
+public class VirtualFileSystemConfiguration
+        implements Configuration {
+
+    private static final Logger log=Logger.getLogger(VirtualFileSystemConfiguration.class.getName());
+    
+    private static FileObject rootDirectory = null;
+    private static Map<String, Object> specialEntries =
+            new HashMap<String, Object>();
+    private Configuration delegate = null;
+
+    /** Inject the working directory for this application  (which might actually
+    be a jar file).  This injection is
+    done using reflection by the ServiceStarterDeployer when the application
+    is setup.  This way, the Configuration can be loaded without any hard-coded
+    directories, etc.
+    @param workingDirectory
+     */
+    public static void setWorkingDirectory(File workingDirectory) {
+        try {
+            if (workingDirectory.isDirectory()) {
+                FileObject root = VFS.getManager().toFileObject(workingDirectory);
+                VirtualFileSystemConfiguration.rootDirectory = root;
+            } else { /* Try to create a virtual file system based on the file. */
+                FileObject rootFileObject = VFS.getManager().toFileObject(workingDirectory);
+                FileObject root = VFS.getManager().createFileSystem(Strings.JAR, rootFileObject);
+                VirtualFileSystemConfiguration.rootDirectory = root;
+            }
+        } catch (FileSystemException ex) {
+            /* Problem here is that we can't just throw the exception,
+            because we expect to be called reflectively from code in a 
+            different classloader, that won't have the exception class.
+            So, we have to instead throw an exception that is part of the 
+            jre platform.
+             */
+            log.log(Level.SEVERE, "Problem setting working directory", ex);
+            throw new RuntimeException(ex.getMessage());
+        }
+
+    }
+
+    public static FileObject getRootDirectory() {
+        return rootDirectory;
+    }
+
+    /**
+    Set the value of a 'Special Entry' as defined in ConfigurationFile, that
+    can be accessed within the configuration by using the '$entryName'
+    construct.
+    @param name The name of the special entry, which must start with '$'.
+    @param o The object to store.
+     */
+    public static void putSpecialEntry(String name, Object o) {
+        specialEntries.put(name, o);
+    }
+
+    public VirtualFileSystemConfiguration(String[] options, ClassLoader cl) throws ConfigurationException {
+
+        /* no options; just delegate. */
+        if (options == null || options.length == 0) {
+            delegate = new MyConfigurationFile(options, cl);
+            return;
+        }
+
+        /* No file called for; just delegate. */
+        if (Strings.DASH.equals(options[0])) {
+            delegate = new MyConfigurationFile(options, cl);
+            return;
+        }
+
+        /* Else, find the configuration file inside the working directory and
+        open it.
+        TODO: Should probably check to make sure that the supplied file
+        name does not include absolute path or '..' path, i.e. make sure
+        that the resolved file is actually a descendant of the working
+        directory.
+         */
+        Reader reader = null;
+        try {
+            FileObject configFile = rootDirectory.resolveFile(options[0]);
+            reader = new InputStreamReader(configFile.getContent().getInputStream());
+            delegate = new MyConfigurationFile(reader, options, cl);
+        } catch (FileSystemException ex) {
+            throw new ConfigurationNotFoundException(options[0], ex);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException ex) {
+                    throw new ConfigurationException(Strings.ERROR_CLOSING_FILE, ex);
+                }
+            }
+        }
+
+    }
+
+    public Object getEntry(String component, String name, Class type) throws ConfigurationException {
+        return delegate.getEntry(component, name, type);
+    }
+
+    public Object getEntry(String component, String name, Class type, Object defaultValue) throws ConfigurationException {
+        return delegate.getEntry(component, name, type, defaultValue);
+    }
+
+    public Object getEntry(String component, String name, Class type, Object defaultValue, Object data) throws ConfigurationException {
+        return delegate.getEntry(component, name, type, defaultValue, data);
+    }
+
+    private static class MyConfigurationFile extends ConfigurationFile {
+
+        public MyConfigurationFile(Reader reader, String[] overrides, ClassLoader cl) throws ConfigurationException {
+            super(reader, overrides, cl);
+        }
+
+        public MyConfigurationFile(String[] options, ClassLoader cl) throws ConfigurationException {
+            super(options, cl);
+        }
+
+        @Override
+        protected Object getSpecialEntry(String name) throws ConfigurationException {
+            if (specialEntries.containsKey(name)) {
+                return specialEntries.get(name);
+            }
+            return super.getSpecialEntry(name);
+        }
+
+        @Override
+        protected Class getSpecialEntryType(String name) throws ConfigurationException {
+            if (specialEntries.containsKey(name)) {
+                return specialEntries.get(name).getClass();
+            }
+            return super.getSpecialEntryType(name);
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/security/ContainerCodePolicy.java b/river-container-core/src/main/java/org/apache/river/container/security/ContainerCodePolicy.java
new file mode 100644
index 0000000..713455d
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/security/ContainerCodePolicy.java
@@ -0,0 +1,76 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.security;

+

+import java.security.*;

+import java.util.ArrayList;

+import java.util.Enumeration;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.apache.river.container.MessageNames;

+

+/**

+ Implements the base policy for the container: Anything loaded by the same

+classloader (or one of its ancestors) as this policy has AllPermission.  

+Anything loaded by a different classloader has no permissions (and will 

+assumedly be granted appropriate permissions dynamically).

+ @author trasukg

+ */

+public class ContainerCodePolicy extends Policy {

+    private static final Logger log=

+            Logger.getLogger(ContainerCodePolicy.class.getName(),

+            MessageNames.BUNDLE_NAME);

+    

+    List<ClassLoader> privilegedClassLoaders=new ArrayList<ClassLoader>();

+

+    public ContainerCodePolicy(ClassLoader bootstrapClassLoader) {

+        privilegedClassLoaders.add(bootstrapClassLoader);

+        ClassLoader cl=this.getClass().getClassLoader();

+        while (cl != null) {

+            privilegedClassLoaders.add(cl);

+            cl=cl.getParent();

+        }

+        allPermissions.add(new AllPermission());

+        allPermissions.setReadOnly();

+        noPermissions.setReadOnly();

+    }

+   

+    private PermissionCollection allPermissions=new Permissions();

+    private PermissionCollection noPermissions=new Permissions();

+    

+    @Override

+    public PermissionCollection getPermissions(ProtectionDomain domain) {

+        if (privilegedClassLoaders.contains(domain.getClassLoader()) ) {

+            return copyPermissions(allPermissions);

+        } else {

+            log.log(Level.FINE, MessageNames.POLICY_DECLINED,

+                    new Object[] { domain.getClassLoader() });

+            return copyPermissions(noPermissions);

+        }

+    }

+    

+    PermissionCollection copyPermissions(PermissionCollection orig) {

+        PermissionCollection pc=new Permissions();

+        Enumeration perms=orig.elements();

+        while(perms.hasMoreElements()) {

+            pc.add((Permission) perms.nextElement());

+        }

+        return pc; 

+    }

+}

diff --git a/river-container-core/src/main/java/org/apache/river/container/security/SecurityInitializer.java b/river-container-core/src/main/java/org/apache/river/container/security/SecurityInitializer.java
new file mode 100644
index 0000000..d200975
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/security/SecurityInitializer.java
@@ -0,0 +1,67 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.security;

+

+import java.security.Policy;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import net.jini.security.policy.DynamicPolicyProvider;

+import org.apache.river.container.ConfigurationException;

+import org.apache.river.container.Context;

+import org.apache.river.container.Init;

+import org.apache.river.container.Injected;

+import org.apache.river.container.InjectionStyle;

+import org.apache.river.container.MessageNames;

+

+/**

+ This class is the container component that sets up the security manager and

+ dynamic policy provider.

+

+ @author trasukg

+ */

+public class SecurityInitializer {

+

+    private static Logger log =

+            Logger.getLogger(SecurityInitializer.class.getName(),

+            MessageNames.BUNDLE_NAME);

+    @Injected(style = InjectionStyle.BY_TYPE)

+    private Context context;

+

+    @Injected

+    private ClassLoader bootstrapClassLoader;

+    

+    @Init

+    public void initialize() {

+        Policy basePolicy = new ContainerCodePolicy(bootstrapClassLoader);

+        DynamicPolicyProvider policy = new DynamicPolicyProvider(basePolicy);

+        Policy.setPolicy(policy);

+        

+        context.put(org.apache.river.container.Strings.SECURITY_POLICY, policy);

+        

+        System.setSecurityManager(new SecurityManager());

+        

+        Policy installedPolicy = Policy.getPolicy();

+        if (installedPolicy != policy) {

+            throw new ConfigurationException(MessageNames.SECURITY_INIT_WRONG_POLICY,

+                    installedPolicy);

+        }

+        

+        log.log(Level.INFO, MessageNames.SECURITY_INIT_SUCCEEDED);

+

+    }

+}

diff --git a/river-container-core/src/main/java/org/apache/river/container/work/BasicWorkManager.java b/river-container-core/src/main/java/org/apache/river/container/work/BasicWorkManager.java
new file mode 100644
index 0000000..91063f6
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/work/BasicWorkManager.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+
+ */
+package org.apache.river.container.work;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.river.container.Init;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.Shutdown;
+import org.apache.river.container.Strings;
+
+/**
+ *
+ * A Basic implementation of WorkManager that runs the work threads through a
+ * ThreadPoolExecutor.
+ *
+ * @author trasukg
+ */
+public class BasicWorkManager implements WorkManager {
+
+    private static final Logger log = Logger.getLogger(BasicWorkManager.class.getName(), MessageNames.BUNDLE_NAME);
+    ExecutorService executor = null;
+    ScheduledExecutorService scheduledExecutor=null;
+    private MyThreadFactory threadFactory = null;
+    private String name = Strings.UNNAMED;
+
+    public BasicWorkManager() {
+        threadFactory = new MyThreadFactory();
+        executor = Executors.newCachedThreadPool(threadFactory);
+        scheduledExecutor=
+                Executors.newSingleThreadScheduledExecutor(threadFactory);
+        
+    }
+
+    public BasicWorkManager(String name) {
+        this.name = name;
+        threadFactory = new MyThreadFactory();
+        executor = Executors.newCachedThreadPool(threadFactory);
+        scheduledExecutor=
+                Executors.newSingleThreadScheduledExecutor(threadFactory);
+    }
+
+    synchronized int getActiveCount() {
+        return threadFactory.threadGroup.activeCount();
+    }
+
+    @Override
+    public void queueTask(ClassLoader contextClassLoader, Runnable task) {
+        ClassLoader classLoaderToUse =
+                contextClassLoader != null ? contextClassLoader : Thread.currentThread().getContextClassLoader();
+        executor.execute(new TaskHolder(task, classLoaderToUse));
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(ClassLoader contextClassLoader, Runnable command, long delay, TimeUnit unit) {
+        ClassLoader classLoaderToUse =
+                contextClassLoader != null ? contextClassLoader : Thread.currentThread().getContextClassLoader();
+        return scheduledExecutor.schedule(new TaskHolder(command, classLoaderToUse), delay, unit);
+    }
+
+    private class TaskHolder implements Runnable {
+
+        Runnable task = null;
+        ClassLoader contextClassLoader = null;
+        ClassLoader originalClassLoader = null;
+
+        TaskHolder(Runnable task, ClassLoader contextClassLoader) {
+            this.task = task;
+            this.contextClassLoader = contextClassLoader;
+        }
+
+        @Override
+        public void run() {
+            originalClassLoader = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(contextClassLoader);
+            try {
+                task.run();
+            } finally {
+                Thread.currentThread().setContextClassLoader(originalClassLoader);
+            }
+        }
+    }
+
+    @Init
+    public void init() {
+        log.info(MessageNames.BASIC_WORK_MANAGER_INITIALIZED);
+    }
+
+    @Shutdown
+    public void shutdown() {
+        executor.shutdownNow();
+        scheduledExecutor.shutdownNow();
+    }
+
+    public void interrupt() {
+        threadFactory.threadGroup.interrupt();
+    }
+    
+    private class MyThreadFactory implements ThreadFactory {
+
+        private ThreadGroup threadGroup = new ThreadGroup(name);
+        private int index = 0;
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(threadGroup, r);
+            t.setName(name + Strings.DASH + index++);
+            log.log(Level.FINE, MessageNames.CREATED_THREAD,
+                    new Object[]{t.getName(), t.getThreadGroup().getName()});
+            return t;
+        }
+    }
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/work/ContextualWorkManager.java b/river-container-core/src/main/java/org/apache/river/container/work/ContextualWorkManager.java
new file mode 100644
index 0000000..8b6e742
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/work/ContextualWorkManager.java
@@ -0,0 +1,73 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.work;

+

+import java.util.ArrayList;

+import java.util.List;

+import org.apache.river.container.Strings;

+

+/**

+

+ @author trasukg

+ */

+public class ContextualWorkManager {

+

+    List<Context> contexts=new ArrayList<Context>();

+    

+    public WorkingContext createContext(String name) {

+        Context context=new Context(name);

+        contexts.add(context);

+        return context;

+    }

+    

+    private class Context implements WorkingContext {

+        String name=Strings.UNNAMED;

+

+        public String getName() {

+            return name;

+        }

+

+        public Context(String name) {

+            this.name=name;

+            workManager=new BasicWorkManager(name);

+        }

+        

+        BasicWorkManager workManager=null;

+        

+        @Override

+        public WorkManager getWorkManager() {

+            return workManager;

+        }

+

+        @Override

+        public int getActiveThreadCount() {

+            return workManager.getActiveCount();

+        }

+

+        @Override

+        public void shutdown() {

+            workManager.shutdown();

+        }

+

+        @Override

+        public void interrupt() {

+            workManager.interrupt();

+        }

+        

+    }

+}

diff --git a/river-container-core/src/main/java/org/apache/river/container/work/WorkManager.java b/river-container-core/src/main/java/org/apache/river/container/work/WorkManager.java
new file mode 100644
index 0000000..1856d63
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/work/WorkManager.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.work;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
+ * Interface for the container's workload manager. System objects use an
+ * instance of this interface to request the container to perform work on their
+ * behalf. Using a centralized workload manager allows the container to both
+ * control the scheduling of the workload and provide instrumentation on the
+ * workload that might be useful for debugging or performance management.
+ *
+ * TODO: Need to have some way of grouping tasks, then killing off a task and
+ * all its subtasks (thread groups etc) for shutdown purposes.
+ *
+ * @author trasukg
+ */
+public interface WorkManager {
+
+    /**
+     * Queue a task for execution.
+     *
+     * @param taskClass Indicates what type of task this is. The implementation
+     * may use this information to assign the task to one of several execution
+     * queues.
+     *
+     * @param contextClassLoader The context classloader that should be used
+     * when running the task.
+     *
+     * @param task The task to be run.
+     */
+    public void queueTask(ClassLoader contextClassLoader,
+            Runnable task);
+
+    /**
+     * Schedule a task for future execution
+     *
+     * @param command
+     * @param delay
+     * @param unit
+     * @return
+     */
+    ScheduledFuture<?> schedule(ClassLoader contextClassLoader,
+            Runnable command,
+            long delay,
+            TimeUnit unit);
+}
diff --git a/river-container-core/src/main/java/org/apache/river/container/work/WorkingContext.java b/river-container-core/src/main/java/org/apache/river/container/work/WorkingContext.java
new file mode 100644
index 0000000..56b2a0a
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/work/WorkingContext.java
@@ -0,0 +1,47 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.work;

+

+/**

+

+ @author trasukg

+ */

+public interface WorkingContext {

+    /**

+    Retrieve the instance of the WorkManager interface that goes with this

+    context.

+    @return The WorkManager instance.

+    */

+    WorkManager getWorkManager();

+    

+    /**

+    Answer how many threads are currently active in this context.

+    @return number of active threads.

+    */

+    int getActiveThreadCount();

+    

+    /**

+    Attempt to stop all threads in the context by interrupting them.

+    */

+    void shutdown();

+    

+    /** 

+     * Interrupt all threads associated with this working context.

+     */

+    void interrupt();

+}

diff --git a/river-container-core/src/main/jjtree/ClasspathExpressionParser.jjt b/river-container-core/src/main/jjtree/ClasspathExpressionParser.jjt
new file mode 100644
index 0000000..9cacf94
--- /dev/null
+++ b/river-container-core/src/main/jjtree/ClasspathExpressionParser.jjt
@@ -0,0 +1,148 @@
+options {

+  LOOKAHEAD = 1;

+  CHOICE_AMBIGUITY_CHECK = 2;

+  OTHER_AMBIGUITY_CHECK = 1;

+  STATIC = false;

+  DEBUG_PARSER = false;

+  DEBUG_LOOKAHEAD = false;

+  DEBUG_TOKEN_MANAGER = false;

+  ERROR_REPORTING = true;

+  JAVA_UNICODE_ESCAPE = false;

+  UNICODE_INPUT = false;

+  IGNORE_CASE = false;

+  USER_TOKEN_MANAGER = false;

+  USER_CHAR_STREAM = false;

+  BUILD_PARSER = true;

+  BUILD_TOKEN_MANAGER = true;

+  SANITY_CHECK = true;

+  BUILD_NODE_FILES = true;

+  FORCE_LA_CHECK = false;

+  MULTI = true;

+  NODE_DEFAULT_VOID = true;

+  VISITOR = true;

+  NODE_CLASS= "ASTNode";

+}

+

+PARSER_BEGIN(ClasspathExpressionParser)

+

+package org.apache.river.container.classloading;

+

+import java.io.Reader;

+import java.util.ArrayList;

+import java.util.List;

+

+import java.util.logging.Logger;

+

+public class ClasspathExpressionParser {

+	private static final Logger log=

+		Logger.getLogger(ClasspathExpressionParser.class.getName());

+

+

+}

+PARSER_END(ClasspathExpressionParser)

+

+/* WHITE SPACE */

+

+SKIP :

+{

+  " "

+| "\t"

+| "\n"

+| "\r"

+| "\f"

+| <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")>

+| <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/">}

+

+TOKEN :

+{

+  	< SYMBOL:

+		["A"-"Z", "a"-"z", "_"] (["0"-"9", "A"-"Z", "a"-"z", ".", "_", "-", "$"])*>

+|	< STRING_LITERAL:

+ 	("\""

+    (   (~["\"","\\","\n","\r"])

+        | ("\\"

+            ( ["n","t","b","r","f","\\","'","\""]

+            | ["0"-"7"] ( ["0"-"7"] )?

+            | ["0"-"3"] ["0"-"7"] ["0"-"7"]

+            )

+          )

+      )*

+      "\"" )

+	| ("\'"

+    	(   (~["'","\\","\n","\r"])

+        | ("\\"

+            ( ["n","t","b","r","f","\\","'","\""]

+            | ["0"-"7"] ( ["0"-"7"] )?

+            | ["0"-"3"] ["0"-"7"] ["0"-"7"]

+            )

+          )

+      )*

+      "\'") >

+		{

+			/* Remove the leading and trailing quotes. */

+			image.deleteCharAt(image.length() -1);

+			image.deleteCharAt(0);

+			matchedToken.image=image.toString();

+		}

+|  	< COMMA: "," >

+|       < COLON: ":" >

+|       < LPAREN: "(" >

+|       < RPAREN: ")" >

+}

+

+void cpExpression() #cpExpression:

+{

+    log.finest("cpExpression()");

+}

+{

+    cpClause() ( <COLON> cpClause())*

+}

+

+void cpClause() #cpClause:

+{

+    log.finest("cpClause()");

+}

+{

+    symbol() [ <LPAREN> filterExpression() <RPAREN>]

+}

+

+void filterExpression():

+{

+    log.finest("filterExpression()");

+}

+{

+    filterClause() ( <COMMA> filterClause())*

+}

+

+void filterClause():

+{

+    log.finest("filterClause()");

+}

+{

+    symbol() | stringLiteral()

+}

+

+

+void symbol() #symbol:

+{

+        log.finest("symbol()");

+        Token t=null;

+}

+{

+        t=<SYMBOL> {

+            jjtThis.setValue(t.image);

+        }

+}

+

+void stringLiteral() #stringLiteral:

+{

+        log.finest("stringLiteral()");

+        Token t=null;

+}

+{

+        t=<STRING_LITERAL> {

+            jjtThis.setValue(t.image);

+        }

+}

+

+

diff --git a/river-container-core/src/main/jjtree/DeployerConfigParser.jjt b/river-container-core/src/main/jjtree/DeployerConfigParser.jjt
new file mode 100644
index 0000000..33c7728
--- /dev/null
+++ b/river-container-core/src/main/jjtree/DeployerConfigParser.jjt
@@ -0,0 +1,413 @@
+options {

+  LOOKAHEAD = 1;

+  CHOICE_AMBIGUITY_CHECK = 2;

+  OTHER_AMBIGUITY_CHECK = 1;

+  STATIC = false;

+  DEBUG_PARSER = false;

+  DEBUG_LOOKAHEAD = false;

+  DEBUG_TOKEN_MANAGER = false;

+  ERROR_REPORTING = true;

+  JAVA_UNICODE_ESCAPE = false;

+  UNICODE_INPUT = false;

+  IGNORE_CASE = false;

+  USER_TOKEN_MANAGER = false;

+  USER_CHAR_STREAM = false;

+  BUILD_PARSER = true;

+  BUILD_TOKEN_MANAGER = true;

+  SANITY_CHECK = true;

+  BUILD_NODE_FILES = true;

+  FORCE_LA_CHECK = false;

+  MULTI = true;

+  NODE_DEFAULT_VOID = true;

+  VISITOR = true;

+  NODE_CLASS= "ASTNode";

+}

+

+PARSER_BEGIN(DeployerConfigParser)

+

+package org.apache.river.container.deployer;

+

+import java.io.InputStream;

+import java.io.Reader;

+import java.io.InputStreamReader;

+import java.util.ArrayList;

+import java.util.List;

+

+import java.util.logging.Logger;

+

+

+public class DeployerConfigParser {

+	private static final Logger log=

+		Logger.getLogger(DeployerConfigParser.class.getName());

+

+    public static ASTconfig parseConfig(InputStream in)

+	throws ParseException {

+		Reader r=new InputStreamReader(in);

+		DeployerConfigParser parser=new DeployerConfigParser(r);

+                parser.config();

+                return (ASTconfig) parser.jjtree.popNode();

+    }

+

+}

+PARSER_END(DeployerConfigParser)

+

+/* WHITE SPACE */

+

+SKIP :

+{

+  " "

+| "\t"

+| "\n"

+| "\r"

+| "\f"

+| <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")>

+| <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/">}

+

+TOKEN :

+{

+  < INTEGER_LITERAL: ["0"-"9"] (["0"-"9"])* >

+|  < FLOATING_POINT_LITERAL:

+        (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? (["f","F","d","D"])?

+      | "." (["0"-"9"])+ (<EXPONENT>)? (["f","F","d","D"])?

+      | (["0"-"9"])+ <EXPONENT> (["f","F","d","D"])?

+      | (["0"-"9"])+ (<EXPONENT>)? ["f","F","d","D"]

+	  | ["0"-"9"] (["0"-"9"])*

+  >

+|  < #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >

+|  < LONG_LITERAL: ["0"-"9"] (["0"-"9"])* (["l", "L"]) >

+| 	< GRANT: "grant" >

+|       < CLASSLOADER: "classloader">

+|       < CODEBASE: "codebase">

+|       < CONFIGURATION: "configuration">

+|       < JARS: "jars">

+|       < PARENT: "parent">

+| 	< AND: "and" >

+| 	< OR: "or" >

+| 	< NOT: "not" >

+| 	< TRUE: "true" >

+| 	< FALSE: "false" >

+|  	< SYMBOL:

+		["A"-"Z", "a"-"z", "_"] 

+                    (["0"-"9", "A"-"Z", "a"-"z", ".", "_", "-", "$"])*>

+|	< STRING_LITERAL:

+ 	("\""

+    (   (~["\"","\\","\n","\r"])

+        | ("\\"

+            ( ["n","t","b","r","f","\\","'","\""]

+            | ["0"-"7"] ( ["0"-"7"] )?

+            | ["0"-"3"] ["0"-"7"] ["0"-"7"]

+            )

+          )

+      )*

+      "\"" )

+	| ("\'"

+    	(   (~["'","\\","\n","\r"])

+        | ("\\"

+            ( ["n","t","b","r","f","\\","'","\""]

+            | ["0"-"7"] ( ["0"-"7"] )?

+            | ["0"-"3"] ["0"-"7"] ["0"-"7"]

+            )

+          )

+      )*

+      "\'") >

+		{

+			/* Remove the leading and trailing quotes. */

+			image.deleteCharAt(image.length() -1);

+			image.deleteCharAt(0);

+			matchedToken.image=image.toString();

+		}

+|	< PLUS: "+" >

+|	< MINUS: "-" >

+|	< STAR: "*" >

+|	< OVER: "/" >

+|	< HOOK: "?" >

+|	< COLON: ":" >

+| 	< EQUALS: "=" >

+| 	< GT: ">" >

+|	< LT: "<" >

+| 	< LTE: "<=" >

+|	< GTE: ">=" >

+|	< NEQ: "<>" >

+| 	< COMMA: "," >

+|       < LBRACE: "{" >

+|       < RBRACE: "}" >

+|       < LPAREN: "(" >

+|       < RPAREN: ")" >

+|       < SEMICOLON: ";">

+

+}

+

+void config() #config:

+{

+    log.fine("config()");

+}

+{

+    (grant() | classloader() | configuration())*

+}

+

+void grant() #grant:

+{

+    log.fine("grant()");

+}

+{

+    <GRANT> "{" (permission())* "}"

+}

+

+void permission() #permission:

+{

+    log.fine("permission()");

+}

+{

+    symbol() [literal() [literal()]]";"

+}

+

+void classloader() #classloader:

+{

+    log.fine("classloader()");

+}

+{

+    <CLASSLOADER> <LBRACE> parent() [jars()] [codebase()] <RBRACE>

+}

+

+void jars() #jars:

+{ 

+    log.fine("jars()");

+}

+{

+    <JARS> <LBRACE> classpath() <RBRACE>

+}

+

+void parent() #parent:

+{

+    log.fine("parent()");

+}

+{

+    <PARENT> symbol() <SEMICOLON>

+}

+

+void codebase() #codebase:

+{

+    log.fine("codebase()");

+}

+{

+    <CODEBASE> <LBRACE> symbol() (<COMMA> symbol())* <RBRACE>

+}

+

+void configuration() #configuration:

+{

+    log.fine("configuration()");

+}

+{

+    <CONFIGURATION> <LBRACE> (configEntry())* <RBRACE>

+}

+

+void classpath() #classpath:

+{

+    log.fine("classpath()");

+}

+{

+    cpEntry() (<COMMA> cpEntry() )*

+}

+

+void cpEntry() #cpEntry:

+{

+    log.fine("cpEntry()");

+}

+{

+    symbol() [ <LPAREN> filterExpression() <RPAREN>]

+}

+

+void filterExpression():

+{

+    log.finest("filterExpression()");

+}

+{

+    filterClause() ( <COMMA> filterClause())*

+}

+

+void filterClause():

+{

+    log.finest("filterClause()");

+}

+{

+    symbol() | literal()

+}

+

+void configEntry() #configEntry:

+{

+    log.fine("configEntry()");

+}

+{

+    symbol() <EQUALS> symbol() <SEMICOLON>

+}

+

+void primary() :

+{

+	log.fine("primary()");

+	String t=null;

+}

+{

+	literal()

+	| "(" expression() ")"

+}

+

+void literal() #literal:

+{

+	Token t=null;

+	log.fine("literal");

+}

+{

+   	t=<INTEGER_LITERAL> {

+                log.fine("integer literal " + t.image);

+                jjtThis.setValue(new Integer(t.image));

+	}

+        |

+   	t=<FLOATING_POINT_LITERAL> {

+                log.fine("floating-point literal " + t.image);

+                jjtThis.setValue(Float.parseFloat(t.image));

+	}

+	|

+   	t=<LONG_LITERAL> {

+                log.fine("long literal " + t.image);

+                jjtThis.setValue(Long.parseLong(t.image));

+	}

+	|

+	t=<STRING_LITERAL>  { 

+                jjtThis.setValue(t.image);

+        }

+	|

+	t=<TRUE>   { 

+                jjtThis.setValue(true);

+        }

+	|

+	t=<FALSE>  {

+                jjtThis.setValue(false);

+        }

+}

+

+void symbol() #symbol:

+{

+        log.fine("symbol()");

+        Token t=null;

+}

+{

+        t=<SYMBOL> {

+            jjtThis.setValue(t.image);

+        }

+}

+

+void expression() :

+{

+	log.fine("expression()");

+}

+{

+	booleanExpression() choiceTerm()

+}

+

+void additiveExpression() :

+{

+	log.fine("additiveExpression()");

+}

+{

+	additiveTerm() additiveList()

+}

+

+void booleanExpression():

+{

+	log.fine("booleanExpression()");

+}

+{

+	booleanTerm() booleanList()

+}

+

+void booleanTerm():

+{

+	log.fine("booleanTerm()");

+}

+{

+	<NOT> conditionalExpression() #booleanNot(1)

+	| conditionalExpression()

+}

+

+void booleanList():

+{

+	log.fine("booleanList()");

+}

+{

+	(

+		<AND> booleanTerm() #booleanAnd(2)

+		| <OR> booleanTerm() #booleanOr(2)

+	) *

+}

+

+void conditionalExpression():

+{

+	log.fine("conditionalExpression()");

+}

+{

+	additiveExpression() conditionalList()

+}

+

+void conditionalList() :

+{

+	log.fine("conditionalList()");

+}

+{

+	(

+		<EQUALS> additiveExpression() #equals(2)

+		| <LT> additiveExpression() #lessThan(2)

+		| <GT> additiveExpression() #greaterThan(2)

+		| <LTE> additiveExpression() #lessThanOrEqual(2)

+		| <GTE> additiveExpression() #greaterThanOrEqual(2)

+		| <NEQ> additiveExpression() #notEqual(2)

+	) ?

+}

+

+void choiceTerm() :

+{

+}

+{

+	( <HOOK> expression() <COLON> expression() #choice(3)

+	 )?

+}

+

+void additiveList() :

+{

+	log.fine("additiveList()");

+}

+{

+	(

+		<PLUS> additiveTerm() #plus(2)

+		| <MINUS> additiveTerm() #minus(2)

+	) *

+}

+

+void additiveTerm():

+{

+	log.fine("additiveTerm()");

+}

+{

+	multiplicativeTerm() multiplicativeList()

+}

+

+void multiplicativeTerm():

+{

+	log.fine("multiplicativeTerm()");

+}

+{

+	primary()

+}

+

+void multiplicativeList() :

+{

+	log.fine("multiplicativeList()");

+}

+{

+	(

+		<STAR> multiplicativeTerm() #multiply(2)

+		| <OVER> multiplicativeTerm() #divide(2)

+	) *

+}

+

+

+

diff --git a/river-container-core/src/main/resources/org/apache/river/container/Messages.properties b/river-container-core/src/main/resources/org/apache/river/container/Messages.properties
new file mode 100644
index 0000000..cdfddcd
--- /dev/null
+++ b/river-container-core/src/main/resources/org/apache/river/container/Messages.properties
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+addedPlatformCodebaseJar=Added platform codebase jar ''{0}''.
+addingClasspathEntry=Adding classpath entry ''{0}''.
+annotatedObjectDeployerHasUnresolvedDependencies=Initialization failed because \
+one or more dependencies flagged with @Injected or @Name annotations \
+are unresolved.
+badClasspathExpression=Bad Classpath Expression {0} {1}.
+badMemberForInjectedAnnotation=@Injected annotation must be applied to a field or\
+ setter method only: Member {1} on class {0} doesn't qualify.
+badMemberForNameAnnotation=@Name annotation must be applied to a String field or\
+ setter method only: Member {1} on class {0} doesn't qualify.
+basicWorkManagerInitialized=Basic Work Manager was initialized.
+callingMain=Calling main method on class ''{0}'' with arguments {1}.
+cantConvertException=Can''t convert type ''{0}'' to ''{1}''.
+cantReadStartProperties=Can''t read starter configuration file ''{0}'' in ''{1}''.
+circularClasspath=Definition of classpath ''{0}'' contains a circular reference.
+classLoaderIs=ClassLoader for class {0} is {1}.
+classpathUndefined=No definition found for classpath id ''{0}''.
+classServerBadRequest=bad request \"{0}\" from {1}:{2}
+classServerErrorAcceptingConnections=Class Server was terminated due to IOException while accepting connections.
+classServerEstablished=Class Server established on host {0} port {1}.
+classServerExceptionDuringShutdown=Class Server caught an exception during shutdown, which was ignored.
+classServerExceptionGettingBytes=Class Server caught an exception while getting bytes to serve the request.
+classServerExceptionWritingResponse=Class Server caught an exception writing the response, so terminated the response.
+classServerInitFailed=Class Server initialization failed.
+classServerNoContentFound=Class Server has no content for path ''{0}''.
+classServerRejectedPath=Rejected request for path ''{0}'' (returning 404).
+classServerReceivedProbe={0} probed from {1}:{2}
+classServerReceivedRequest={0} requested from {1}:{2}
+classServerTerminated=Class Server terminated as part of normal shutdown on host {0} port {1}.
+codeSourceIs=CodeSource for service in ''{0}'' is ''{1}''.
+completedServiceDeployment=Completed deployment of service in {0}.
+configFile=Configuration file is ''{0}''.
+configuredClasspath=The configured classpath with id ''{0}'' is {1}.
+contextItem=Context key {0} refers to ''{1}''.
+createdThread=Created thread named ''{0}'' in thread group ''{1}''.
+duplicateClasspath=Duplicate class path entry for id ''{0}''.
+exceptionThrown=Exception thrown:\n{0}
+exceptionWhileStopping=Exception thrown during stop operation:\n{0}
+failedCleanShutdown=Application ''{0}'' failed to shutdown cleanly, so we're interrupting it.
+failedDeployService=Deployment of service archive at ''{0}'' failed.
+failedReadProperties=Failed to read one or more properties files.
+foundNoServiceArchives=Found no service archives for deployment dir ''{0}''.
+foundServiceArchives=Found {0} service archives in deployment dir ''{1}''.
+illegalArgumentException=An operation threw an IllegalArgumentException.
+illegalAccessException=An operation threw an IllegalAccessException.
+invalidClasspathEntry=Invalid classpath entry: {0}
+invocationTargetException=An operation threw an InvocationTargetException.
+initializingEventTable=Initializing event table for class ''{0}''.
+initializationException=An exception has occured while initializing the container.
+initMethodHasParameters=A method flagged as @Init must take no parameters.  \
+Method ''{1}'' on class ''{0}'' has parameters.
+initMethodIsntVoid=A method flagged as @Init must be void return type.  \
+Method ''{1}'' on class ''{0}'' returns ''{2}''.
+inject=Injecting {2} into member ''{1}'' of deployed object {0}.
+missingPropertyEntry="Properties file ''{0}'' is missing entry for ''{1}''.
+missingSpecialValue=Deployer configuration file ''{0}'' calls for a special entry called\n\
+''{1}'' to be created with value expression ''{2}'', but the value resolves to null.\n\
+This is unlikely to be the desired behavior, so check to see if you''re missing\n\
+value ''{2}'' in other configurations or command line parameters.
+nThreadsLeft=Application ''{0}'' has {1} threads currently running.
+noDeploymentDirectory=No deployment directory called {0} found in {1}; \
+skipping deployment.
+parentClassLoaderIs=Parent class loader is {0}.
+policyDeclined=No permissions granted to protection domain with classloader {0}.
+profileConfigurationException=Failed to read the configuration for profile {0}.
+profileConfigLoading=Loading the profile configuration with classloader {0}.
+readProperties=...properties read were {0}.
+readPropertiesFile=Read properties file ''{0}''.
+readingObject=Reading instance of {1} named ''{0}'' for unresolved dependencies.
+readingObject.memberCount={0} members found.
+readingObject.annotatedMemberFound=Member ''{0}'' is annotated @Injected.
+readingObject.nonAnnotatedMemberFound=Member ''{0}'' is not annotated @Injected.
+receivedStart=Received start event while in state {0}.
+securityInitializationFailed=Failed to initialize security subsystem.
+securityInitializationSucceeded=Security Manager and Dynamic Policy successfully installed.            
+securityInitializationWrongPolicy=After security manager setup, the wrong policy is installed: {0}.
+serviceParentClassloaderIs=Parent of service classloader is {0}.
+showCommandLineArguments=Command line arguments were: {0}.
+shutdownFailed=Application ''{0}'' has failed to shut down - there are threads still running.
+shutdownMethodHasParameters=A method flagged as @Shutdown must take no parameters.  \
+Method ''{1}'' on class ''{0}'' has parameters.
+shutdownMethodIsntVoid=A method flagged as @Shutdown must be void return type.  \
+Method ''{1}'' on class ''{0}'' returns ''{2}''.
+starterServiceDeployerFailedInit=Starter-Service deployer has failed to initialize.
+starterServiceDeployerInitialized=Starter-Service deployer named ''{0}'' completed \
+initialization.
+starterServiceDeployerStarting=Starter-Service deployer named ''{0}'' is being \
+initialized.
+startupDeployerFailedInit=Starter-Service deployer has failed to initialize.
+startupDeployerInitialized=Starter-Service deployer named ''{0}'' completed \
+initialization.
+startupDeployerStarting=Starter-Service deployer named ''{0}'' is being \
+initialized.
+systemClassLoaderIs=System classloader is ''{0}'' with classpath {1}.
+unresolvedDependency=Object {0} has an unresolved dependent member ''{1}'' \
+(name=''{2}'').
+unsupportedElement=Element type {0} is currently unsupported.
diff --git a/river-container-core/src/main/resources/org/apache/river/container/hsm/Messages.properties b/river-container-core/src/main/resources/org/apache/river/container/hsm/Messages.properties
new file mode 100644
index 0000000..32f94c5
--- /dev/null
+++ b/river-container-core/src/main/resources/org/apache/river/container/hsm/Messages.properties
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+applyingEventToState=Applying event {0} to state {1} instance {2}.
+beginningCompile=Begginning state machine compilation on {0}.
+compileCompleted=Completed compile on {0}.
+cantResolveTransitionsForClass=Can''t find any valid targets for transition ''{1}'' from the context of state class ''{0}''.
+entryMethodIsntVoid=Methods annotated with @OnEntry need to return void.  Method {0} returns {1}.
+errorApplyingTransition=Got an error while applying a transition: {0}.
+errorCreatingProxy=Error Creating Proxy.
+errorInvokingTargetMethod=Error invoking target method on state instance.
+errorInstantiating=Error while instantiating the state machine: {0}.
+exitMethodIsntVoid=Methods annotated with @OnExit need to return void.  Method {0} returns {1}.
+guardMethodDoesntReturnBoolean=Methods annotated with @Guard need to return a boolean value.  Method {0} returns {1}.
+metaStateWasInstantiated=MetaState for {0} was instantiated as {1}.
+multipleExceptionsThrown=Execution of the event threw multiple exceptions.
+noParentInstance=Couldn''t find a parent instance for {0} starting at state class {1}.
+queuedTransition=Queued transition to {0} on {1}.
+runningGuardOnState=Running guard method {0} on state {1} instance {2}.
+runningGuardTransitions=Guard method {0} on state {1} instance {2} returned true - running transitions.
+settingFieldTo=Setting field {0} on {1} to {2}.
+settingInitialSubstate=Setting initial substate field {0} on state {1}.
+storingException=Storing exception {2} thrown while executing {1} on {0}.
diff --git a/river-container-core/src/main/xsd/config.xsd b/river-container-core/src/main/xsd/config.xsd
new file mode 100644
index 0000000..5421210
--- /dev/null
+++ b/river-container-core/src/main/xsd/config.xsd
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+    targetNamespace="http://river.apache.org/xml/ns/container/config/1.0"
+    xmlns:tns="http://river.apache.org/xml/ns/container/config/1.0"
+    jaxb:version="2.0" elementFormDefault="qualified">
+    <xsd:element name="container-config">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:classpath" maxOccurs="unbounded" minOccurs="0"/>
+                <xsd:choice maxOccurs="unbounded">
+                    <xsd:annotation>
+                        <xsd:appinfo>
+                            <jaxb:property name="elements"/>
+                        </xsd:appinfo>
+                    </xsd:annotation>
+                    <xsd:element ref="tns:property"/>
+                    <xsd:element ref="tns:component"/>
+                    <xsd:element name="discovery-context"
+                    type="tns:DiscoveryContextType"/>
+                </xsd:choice>
+                
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:complexType name="DiscoveryContextType">
+        <xsd:sequence>
+            <xsd:element name="locator" type="xsd:anyURI" minOccurs="0"
+            maxOccurs="unbounded"/>
+            <xsd:element name="group" type="xsd:string" minOccurs="0"
+            maxOccurs="unbounded"/>
+        </xsd:sequence>
+        <xsd:attribute name="name" type="xsd:string" use="optional"/>
+    </xsd:complexType>
+    
+    <xsd:element name="classpath">
+        <xsd:complexType>
+            <xsd:simpleContent>
+                <xsd:extension base="xsd:string">
+                    <xsd:attribute name="id" use="required" type="xsd:string"/>
+                    <xsd:attribute name="parent" use="optional" type="xsd:string"/>
+                </xsd:extension>
+            </xsd:simpleContent>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="codebase" type="xsd:string"/>
+
+    <xsd:element name="property">
+        <xsd:complexType>
+            <xsd:attribute name="name" type="xsd:string" use="required"/>
+            <xsd:attribute name="value" type="xsd:string" use="required"/>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="component">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:property" minOccurs="0" maxOccurs="unbounded"/>
+            </xsd:sequence>
+            <xsd:attribute name="name" type="xsd:string" use="optional"/>
+            <xsd:attribute name="class" type="xsd:string" use="required"/>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="service-starter-config">
+        <xsd:annotation>
+            <xsd:documentation>
+                This element is used in the configuration file for the
+                service-starter service deployer component.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:classpath" minOccurs="0"/>
+                <xsd:element ref="tns:codebase" minOccurs="0"/>
+                <xsd:choice maxOccurs="unbounded">
+                    <xsd:annotation>
+                        <xsd:appinfo>
+                            <jaxb:property name="elements"/>
+                        </xsd:appinfo>
+                    </xsd:annotation>
+                    <xsd:element name="discovery-context"
+                    type="tns:DiscoveryContextType"/>
+                </xsd:choice>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+</xsd:schema>
diff --git a/river-container-core/src/test/java/org/apache/river/container/AnnotatedClassDeployerTest.java b/river-container-core/src/test/java/org/apache/river/container/AnnotatedClassDeployerTest.java
new file mode 100644
index 0000000..c482ebb
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/AnnotatedClassDeployerTest.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.Properties;
+import java.util.logging.Logger;
+import java.lang.reflect.Member;
+import java.util.List;
+import java.util.logging.Level;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Test Case for AnnotatedClassDeployer.
+ * @author trasukg
+ */
+public class AnnotatedClassDeployerTest {
+
+    Logger log = Logger.getLogger(AnnotatedClassDeployerTest.class.getName());
+
+    public AnnotatedClassDeployerTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger.getLogger(AnnotatedClassDeployer.class.getName()).setLevel(Level.FINEST);
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+    Context context = new Context();
+    AnnotatedClassDeployer UUT = new AnnotatedClassDeployer();
+
+    @Before
+    public void setUp() {
+        UUT.setContext(context);
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+    Test whether a single injected value is set on a component.
+     */
+    @Test
+    public void testReadsPrivateField() {
+        InjectionHarness harness = new InjectionHarness();
+        UUT.put(harness.getClass().getName(), harness);
+        List<Member> members = UUT.buildMemberList(harness.getClass());
+        assertTrue("No members found", members.size() > 0);
+    }
+
+    /**
+    Test whether a single injected value is set on a component.
+     */
+    @Test
+    public void testInjected() {
+        InjectionHarness harness = new InjectionHarness();
+        UUT.put(harness.getClass().getName(), harness);
+        assertEquals("Context wasn't injected", context, harness.context);
+    }
+
+    /**
+    Init method must be void return, and we should get an exception if it
+    specifies a return value;
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testInitMethodWithNonVoidReturn() {
+        Object harness = new InjectionHarnessWithNonVoidInit();
+        UUT.put(harness.getClass().getName(), harness);
+    }
+
+    /**
+    Init method must take no parameters and we should get an exception if it
+    has parameters;
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testInitMethodWithParameters() {
+        Object harness = new InjectionHarnessWithInitParameters();
+        UUT.put(harness.getClass().getName(), harness);
+    }
+
+    /**
+    Test whether a single injected value is set on a component.
+     */
+    @Test
+    public void testInitCalled() {
+        InjectionHarness harness = new InjectionHarness();
+        UUT.put(harness.getClass().getName(), harness);
+        assertTrue("Init method wasn't called", harness.initialized);
+        assertTrue("Second init method wasn't called", harness.secondInitCalled);
+        assertEquals("Init was called more than once:", 1, harness.initCount);
+    }
+
+    /**
+    Test that if we have two items deployed, one will be injected into the
+    other.
+     */
+    @Test
+    public void testResourceInjected() {
+        log.info("testResourceInjected()");
+        InjectionHarness harness = new InjectionHarness();
+        InjectionHarnessWithDependencies harness2 =
+                new InjectionHarnessWithDependencies();
+        UUT.put(harness.getClass().getName(), harness);
+        UUT.put(harness2.getClass().getName(), harness2);
+        assertEquals(harness, harness2.harness);
+    }
+
+    /**
+    /**
+    Test that if we have two items deployed, one will be injected into the
+    other.
+    
+    This injection should occur even if an item is a subclass of the
+    injected field's type.  In other words, if a field wants a java.util.List
+    for instance, it should get a java.util.ArrayList injected if that's the
+    only available object.
+     */
+    @Test
+    public void testDerivedResourceInjected() {
+        log.info("testResourceInjected()");
+        InjectionHarness harness = new InjectionHarnessSubclass();
+        InjectionHarnessWithDependencies harness2 =
+                new InjectionHarnessWithDependencies();
+        UUT.put(harness.getClass().getName(), harness);
+        UUT.put(harness2.getClass().getName(), harness2);
+        assertEquals(harness, harness2.harness);
+    }
+
+    /**
+    Test that if we have two items deployed, one will be injected into the
+    other.
+     */
+    @Test
+    public void testResourceInjectedByName() {
+        InjectionHarnessWithNamedDependencies harness2 =
+                new InjectionHarnessWithNamedDependencies();
+        UUT.put("this.is.a.name", "Bob");
+        UUT.put(harness2.getClass().getName(), harness2);
+        assertEquals("Bob", harness2.injectedVariable);
+    }
+
+    @Test
+    /**
+    If we don't specify an explicit name as the value of the @Injected
+    annotation, then the injection should be tried based on the 'implied'
+    name of the member, i.e. the field name or property name for a 'setXYZ'
+    method.  Also we need to make sure that 'byType' injection isn't imposed
+    in a silly way, for instance injecting a string to a field, even though
+    there is a string available to the context under a different name.
+     */
+    public void testInjectByImpliedName() {
+        InjectionHarnessWithDependencies h1 = new InjectionHarnessWithDependencies();
+        UUT.put(h1.getClass().getName(), h1);
+        UUT.put("name", "abc");
+        assertEquals("abc", h1.name);
+        /* Shouldn't inject a silly string! */
+        assertEquals(null, h1.otherName);
+    }
+
+    @Test
+    /**
+    If we have a properties field that has a name on it, then it should not
+    be subject to injection by type.
+    */
+    public void noDefaultInjectionByType() {
+        PropertyInjectionHarness pih=new PropertyInjectionHarness();
+        UUT.put(pih.getClass().getName(), pih);
+        Properties p1=new Properties();
+        UUT.put("wrong.properties", p1);
+        assertTrue("Shouldn't have injected properties with the wrong name",
+                pih.properties==null);
+        Properties p2=new Properties();
+        UUT.put("a.properties", p2);
+        assertEquals(p2, pih.properties);
+        
+    }
+    private class InjectionHarness {
+
+        @Injected(style=InjectionStyle.BY_TYPE)
+        Context context = null;
+        boolean initialized = false;
+        boolean secondInitCalled = false;
+        int initCount = 0;
+
+        @Init
+        void init() {
+            initialized = true;
+            initCount++;
+        }
+
+        @Init
+        void initAgain() {
+            secondInitCalled = true;
+        }
+    }
+
+    private class InjectionHarnessSubclass extends InjectionHarness {
+    }
+
+    private class InjectionHarnessWithNonVoidInit {
+
+        @Init
+        public int init() {
+            return -1;
+        }
+    }
+
+    private class InjectionHarnessWithInitParameters {
+
+        @Init
+        public void init(int j) {
+        }
+    }
+
+    private class InjectionHarnessWithDependencies {
+
+        @Injected(style=InjectionStyle.BY_TYPE)
+        InjectionHarness harness = null;
+        @Injected
+        String name = null;
+        @Injected
+        String otherName = null;
+    }
+
+    private class InjectionHarnessWithNamedDependencies {
+
+        @Injected("this.is.a.name")
+        String injectedVariable = null;
+    }
+    
+    private class PropertyInjectionHarness {
+        @Injected("a.properties")
+        Properties properties;
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/BootstrapTest.java b/river-container-core/src/test/java/org/apache/river/container/BootstrapTest.java
new file mode 100644
index 0000000..9a9974b
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/BootstrapTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.logging.Logger;
+import java.net.URL;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class BootstrapTest {
+
+    private static Logger log=Logger.getLogger(BootstrapTest.class.getName());
+
+    public BootstrapTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testFindClasspathURLS() throws Exception {
+        URL[] urls = Bootstrap.findClasspathURLS("abc.jar");
+        log.info("URLs:" + Utils.format(urls));
+        assertEquals("No URLS found.", 1, urls.length);
+    }
+
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/CommonsVFSTest.java b/river-container-core/src/test/java/org/apache/river/container/CommonsVFSTest.java
new file mode 100644
index 0000000..4c83709
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/CommonsVFSTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.List;
+import org.junit.Ignore;
+import java.io.File;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.VFS;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * 
+Exploratory tests to understand and document the behavior of Commons-VFS
+ * @author trasukg
+ */
+public class CommonsVFSTest {
+
+    FileSystemManager fileSystemManager = null;
+
+    public CommonsVFSTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() throws FileSystemException {
+        fileSystemManager = VFS.getManager();
+
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+    Should be able to get the current directory, and it should end with
+    'testfiles/testroot'.
+    @throws Exception
+     */
+    @Test
+    public void testBaseFile() throws Exception {
+        FileObject fo = fileSystemManager.resolveFile(new File("."), ".");
+        System.out.println("fo=" + fo);
+        assertTrue(fo.toString().endsWith("build/testroot"));
+    }
+
+    /**
+    Should be able to get the current directory, and it should end with
+    'testfiles/testroot'.
+    @throws Exception
+     */
+    @Test
+    public void testSuffixSelector() throws Exception {
+        FileObject fo = fileSystemManager.resolveFile(new File("."), "lib");
+        System.out.println("fo=" + fo);
+        assertTrue(fo.toString().endsWith("build/testroot/lib"));
+        List<FileObject> jars=Utils.findChildrenWithSuffix(fo, Strings.DOT_JAR);
+        assertTrue("Didn't get any jar files.", jars.size()>0);
+    }
+
+    /**
+    Make sure we can use the jar:syntax to get to the 'start.properties' file
+    inside the constructed reggie module jar.
+     */
+    @Test
+    public void testFileInReggieModuleJar() throws Exception {
+        FileObject reggieJar =
+                fileSystemManager.resolveFile(new File("../../build/test/files"), "reggie-module.ssar");
+        assertTrue("Bad file:" + reggieJar.toString(), reggieJar.toString().endsWith("reggie-module.ssar"));
+        FileObject reggieJarFS = fileSystemManager.createFileSystem(Strings.JAR, reggieJar);
+
+        FileObject startProperties = reggieJarFS.resolveFile("start.properties");
+        assertNotNull(startProperties);
+        assertTrue("Properties file unreadable:" + startProperties.toString() + " type=" + startProperties.getType(), startProperties.isReadable());
+    }
+
+    /**
+    If we create a virtual file system based on a jar file, we should be
+    able to add other jar files by adding junctions to the root, with the name
+    of the file we're adding.
+    
+        
+    Unfortunately, this theory doesn't pan out...
+    org.apache.commons.vfs.FileSystemException: Attempting to create a nested junction at "null/otherStart.properties".  Nested junctions are not supported.
+        at org.apache.commons.vfs.impl.VirtualFileSystem.addJunction(VirtualFileSystem.java:111)
+    
+     */
+    @Test @Ignore /*Didin't work, see above */
+    public void testFileSystemJunctions() throws Exception {
+        FileObject reggieJar =
+                fileSystemManager.resolveFile(new File("../../build/test/files"), "reggie-module.jar");
+        assertTrue("Bad file:" + reggieJar.toString(), reggieJar.toString().endsWith("reggie-module.jar"));
+        FileObject reggieJarFS = fileSystemManager.createFileSystem(reggieJar);
+
+        FileObject virtRoot = fileSystemManager.createVirtualFileSystem((String) null);
+        virtRoot.getFileSystem().addJunction("/", reggieJarFS);
+        checkPresentAndReadable(virtRoot, "start.properties");
+        FileObject startProperties = virtRoot.resolveFile("start.properties");
+        assertNotNull(startProperties);
+        assertTrue("Properties file unreadable:" + startProperties.toString() + " type=" + startProperties.getType(), startProperties.isReadable());
+        
+        /* Now try to add in a junction to a jar file */
+        virtRoot.getFileSystem().addJunction("otherStart.properties", startProperties);
+        checkPresentAndReadable(virtRoot, "otherStart.properties");
+    }
+    
+    void checkPresentAndReadable(FileObject root, String name) throws FileSystemException {
+        FileObject fo = root.resolveFile(name);
+        assertNotNull(fo);
+        assertTrue("File unreadable:" + fo.toString() + " type=" + fo.getType(), fo.isReadable());
+    }
+}
\ No newline at end of file
diff --git a/river-container-core/src/test/java/org/apache/river/container/PropertiesFileReaderTest.java b/river-container-core/src/test/java/org/apache/river/container/PropertiesFileReaderTest.java
new file mode 100644
index 0000000..9a10fa8
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/PropertiesFileReaderTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container;
+
+import java.util.Properties;
+import java.io.File;
+import org.apache.commons.vfs2.FileSystemManager;
+import java.io.IOException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.VFS;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class PropertiesFileReaderTest {
+
+    public PropertiesFileReaderTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+    PropertiesFileReader UUT = new PropertiesFileReader();
+    Context context = new Context();
+    FileUtility fileUtility = new MockFileUtility();
+
+    @Before
+    public void setUp() {
+        context.put(new AnnotatedClassDeployer());
+        context.put(Strings.FILE_UTILITY, fileUtility);
+        context.put(UUT);
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /** 
+    Check that the MockFileUtility returns the profile directory as the
+    'testfiles' dir.
+    @throws Exception 
+    */
+    @Test
+    public void testMockFileUtility() throws Exception {
+        FileObject fo=fileUtility.getProfileDirectory();
+        
+        System.out.println("fo=" + fo);
+            assertTrue(fo.toString().endsWith("testfiles"));
+    }
+    
+    /**
+    When setup in the context, the config file reader should scan the profile
+    directory for files ending in ".properties", and create a Properties object
+    based on each file.  As such, we should be able to just read the properties
+    from the context.
+    */
+    @Test 
+    public void testReader() {
+        Properties testProps=(Properties) 
+                context.get("property-file-reader-test.properties");
+        assertNotNull("property-file-reader-test.properties wasn't loaded.",
+                testProps);
+        
+        assertEquals("Expected message=Hello World", "Hello World", 
+                testProps.getProperty("message"));
+    }
+    
+    @Test
+    public void testPropertiesInjection() throws Exception {
+        AnnotatedTestHarness harness=new AnnotatedTestHarness();
+        context.put(harness);
+        
+        assertNotNull("property-file-reader-test.properties wasn't loaded.",
+                harness.props);
+        
+        assertEquals("Expected message=Hello World", "Hello World", 
+                harness.props.getProperty("message"));
+        
+    }
+    
+    private class AnnotatedTestHarness {
+        
+        @Injected("property-file-reader-test.properties")
+        Properties props=null;
+        
+    }
+    
+    private class MockFileUtility implements FileUtility {
+
+        @Override
+        public FileObject getWorkingDirectory(String name) throws IOException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public FileObject getProfileDirectory() throws IOException {
+            FileSystemManager fileSystemManager=VFS.getManager();
+            FileObject fo = fileSystemManager.resolveFile(new File("../../testfiles"), ".");
+            
+            return fo;
+        }
+
+        @Override
+        public FileObject getLibDirectory() throws IOException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/classloading/ClasspathFilterParserTest.java b/river-container-core/src/test/java/org/apache/river/container/classloading/ClasspathFilterParserTest.java
new file mode 100644
index 0000000..6fc1222
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/classloading/ClasspathFilterParserTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.classloading;
+
+import java.util.logging.Logger;
+import java.util.List;
+import java.util.logging.Level;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ClasspathFilterParserTest {
+
+    public ClasspathFilterParserTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger.getLogger(ClasspathFilterBuilder.class.getName()).setLevel(Level.ALL);
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+    Does basic test on parsing of the jar specification.
+    Syntax is name.jar(classToServe).
+    @throws Exception
+     */
+    @Test
+    public void testParser() throws Exception {
+        ClasspathFilterBuilder UUT = new ClasspathFilterBuilder();
+        List<ClasspathFilter> cpfs = UUT.parseToFilters("reggie.jar(org.apache.Abc)");
+        ClasspathFilter cpf=cpfs.get(0);
+        assertEquals("reggie.jar", cpf.getJarName());
+        List<Acceptor> actual = cpf.getAcceptors();
+        assertEquals("Wrong number of filter clauses.", 1, actual.size());
+        assertEquals("Filter condition", new ResourceAcceptor("org/apache/Abc.class"), actual.get(0));
+    }
+
+    /**
+    Does basic test on parsing of the jar specification.
+    Syntax is name.jar(classToServe).
+    @throws Exception
+     */
+    @Test
+    public void testParserOnMultipleClasses() throws Exception {
+        ClasspathFilterBuilder UUT = new ClasspathFilterBuilder();
+        String jarSpec = "reggie.jar(org.apache.ABC, org.apache.DEF)";
+        List<ClasspathFilter> cpfs = UUT.parseToFilters(jarSpec);
+        ClasspathFilter cpf=cpfs.get(0);
+        assertEquals("reggie.jar", cpf.getJarName());
+        List<Acceptor> actual = cpf.getAcceptors();
+        assertEquals("Wrong number of filter clauses.", 2, actual.size());
+        assertEquals("Filter condition", new ResourceAcceptor("org/apache/ABC.class"), actual.get(0));
+        assertEquals("Filter condition", new ResourceAcceptor("org/apache/DEF.class"), actual.get(1));
+    }
+
+    /**
+    Does basic test on parsing of the jar specification.
+    Syntax is name.jar(classToServe[,filter]*).
+     If the filter clause is a double-quoted string, then it represents
+     a resource filter rather than a class filter (i.e. it specifies an actual
+     resource in the jar file rather than a class description.
+    @throws Exception
+     */
+    @Test
+    public void testFilterAcceptance() throws Exception {
+        ClasspathFilterBuilder UUT = new ClasspathFilterBuilder();
+        String jarSpec = "reggie.jar(org.apache.ABC, org.apache.DEF, \"META-INF/*\")";
+        List<ClasspathFilter> cpfs = UUT.parseToFilters(jarSpec);
+        ClasspathFilter cpf=cpfs.get(0);
+        assertEquals("reggie.jar", cpf.getJarName());
+        assertTrue(cpf.acceptsResource("org/apache/ABC.class"));
+        assertFalse(cpf.acceptsResource("org/apache/XYZ.class"));
+        assertTrue(cpf.acceptsResource("org/apache/DEF.class"));
+        assertTrue(cpf.acceptsResource("META-INF/start.properties"));
+    }
+
+    /**
+    If there's no class specifications, should accept all class possibilities.
+    @throws Exception
+     */
+    @Test
+    public void testFilterAcceptanceWithJarOnly() throws Exception {
+        ClasspathFilterBuilder UUT = new ClasspathFilterBuilder();
+        String jarSpec = "reggie.jar";
+        List<ClasspathFilter> cpfs = UUT.parseToFilters(jarSpec);
+        ClasspathFilter cpf=cpfs.get(0);
+        assertEquals("reggie.jar", cpf.getJarName());
+        assertTrue(cpf.acceptsResource("org/apache/ABC.class"));
+        assertTrue(cpf.acceptsResource("org/apache/XYZ.class"));
+        assertTrue(cpf.acceptsResource("org/apache/DEF.class"));
+     }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/classloading/VFSClassLoaderTest.java b/river-container-core/src/test/java/org/apache/river/container/classloading/VFSClassLoaderTest.java
new file mode 100644
index 0000000..183ad05
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/classloading/VFSClassLoaderTest.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */package org.apache.river.container.classloading;
+
+import org.apache.river.container.classloading.VirtualFileSystemClassLoader;
+import java.net.URL;
+import java.io.InputStream;
+import java.io.File;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.VFS;
+import org.apache.river.container.Bootstrap;
+import org.apache.river.container.LocalizedRuntimeException;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.Utils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class VFSClassLoaderTest {
+
+    FileSystemManager fileSystemManager = null;
+    FileObject reggieModuleRoot = null;
+    FileObject libRoot=null;
+    ClassLoader extensionLoader = Bootstrap.class.getClassLoader().getParent();
+
+    public VFSClassLoaderTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        fileSystemManager = VFS.getManager();
+        FileObject currentDir = fileSystemManager.toFileObject(new File("."));
+        FileObject reggieModuleJar =
+                currentDir.resolveFile("../test/files/reggie-module.ssar");
+        reggieModuleRoot = fileSystemManager.createFileSystem(Strings.JAR,
+                reggieModuleJar);
+        libRoot=reggieModuleRoot.resolveFile(Strings.LIB);
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+    Just to make sure that we have the base setup correct, ensure that we
+    can read the 'start.properties' file inside the reggie-module jar.
+    @throws Exception
+     */
+    @Test
+    public void testCanReadStartDotProperties() throws Exception {
+        FileObject startProperties = reggieModuleRoot.resolveFile("start.properties");
+        assertNotNull(startProperties);
+        assertTrue("Properties file unreadable:"
+                + startProperties.toString() + " type=" + startProperties.getType(),
+                startProperties.isReadable());
+    }
+
+    /**
+    Create a VFSClassLoader and make sure it throws an exception if we try
+    to add a non-existent jar file to it.
+    Also, test out the addClassPathEntry(root, fileName) method.
+    @throws Exception
+     */
+    @Test
+    public void testNonExistentJarFile() throws Exception {
+        VirtualFileSystemClassLoader UUT =
+                new VirtualFileSystemClassLoader(null, extensionLoader, null);
+        try {
+            UUT.addClassPathEntry(libRoot, "nonexistent.jar");
+            fail("Should have thrown an invalid classpath entry exception");
+        } catch (LocalizedRuntimeException ex) {
+            assertEquals(MessageNames.INVALID_CLASSPATH_ENTRY, ex.getMessageKey());
+        }
+    }
+
+    /**
+    Create a VFSClassLoader and see if we can read a resource file from it.
+    As shown below, we're just adding a classpath entry with no filters or
+    codebase.
+    @throws Exception
+     */
+    @Test
+    public void testClassLoaderResourceLoading() throws Exception {
+        VirtualFileSystemClassLoader UUT =
+                new VirtualFileSystemClassLoader(libRoot, extensionLoader, null);
+        UUT.addClassPathEntry("reggie.jar");
+        InputStream is = UUT.getResourceAsStream("META-INF/PREFERRED.LIST");
+        assertNotNull("Failed to get resource stream for META-INF/PREFERRED.LIST",
+                is);
+    }
+
+    /* Note to self- test for exception cases - bad fsroot, directory not jar, etc.
+     */
+
+    /**
+    The classloader should be able to load a class that's in the jar file,
+    and when we get an instance of that class, it should have the UUT
+    as its classloader.
+    @throws Exception
+     */
+    @Test
+    public void testClassLoading() throws Exception {
+        VirtualFileSystemClassLoader UUT =
+                new VirtualFileSystemClassLoader(libRoot, extensionLoader, null);
+        UUT.addClassPathEntry("reggie.jar");
+        Class c = UUT.loadClass("com.sun.jini.reggie.ClassMapper");
+        assertNotNull(c);
+        assertTrue("Class had wrong classloader:" + c.getClassLoader(),
+                c.getClassLoader()==UUT);
+    }
+
+    /**
+    The classloader should be able to load a class that's in the jar file,
+    and when we get an instance of that class, it should have the UUT
+    as its classloader.
+    @throws Exception
+     */
+    @Test
+    public void testParentClassLoading() throws Exception {
+        VirtualFileSystemClassLoader UUT =
+                new VirtualFileSystemClassLoader(libRoot, extensionLoader, null);
+        UUT.addClassPathEntry("reggie.jar");
+        Class c = UUT.loadClass("java.util.List");
+        assertNotNull(c);
+        assertTrue("Class had wrong classloader:" + c.getClassLoader(),
+                c.getClassLoader()==null);
+        assertTrue("java.util.List".equals(c.getName()));
+    }
+
+    @Test
+    public void testCodebaseAnnotation() throws Exception {
+        VirtualFileSystemClassLoader UUT =
+                new VirtualFileSystemClassLoader(libRoot, extensionLoader, null);
+        UUT.addClassPathEntry("reggie.jar");
+        /* At this point, there should be no urls on the reported codebase. */
+        URL[] actual=UUT.getURLs();
+        assertTrue("Should be no urls, but got " + Utils.format(actual),
+                actual.length==0);
+        URL[] a={ new URL("http://localhost:8080/a.jar")};
+        UUT.setCodebase(a);
+        actual=UUT.getURLs();
+        assertEquals("Should be one urls, but got " + Utils.format(actual),
+                1, actual.length);
+
+    }
+
+    /**
+     We can setup filtered classloading, such that the classloader only
+     supplies classes that match a particular pattern for a given jar.
+     This facility prevents having to create a "subset" jar for cases where
+     we want to have only a few classes loaded by a child class loader.
+     In particular, this is to allow the container liaison classes to be
+     resident in the application's (surrogate's) classloader even though the
+     classes are included in the source tree of the main project (hence in
+     RiverSurrogate.jar).
+     @throws Exception
+     */
+    @Test
+    public void testFilteredClassLoading() throws Exception {
+        VirtualFileSystemClassLoader UUT =
+                new VirtualFileSystemClassLoader(libRoot, extensionLoader, null);
+        UUT.addClassPathEntry("reggie.jar(com.sun.jini.reggie.ClassMapper)");
+        /* We should now be able to load the ClassMapper class, but nothing
+        else.
+        */
+        Class classMapperClass=UUT.loadClass("com.sun.jini.reggie.ClassMapper");
+        assertNotNull("loaded class was null", classMapperClass);
+
+        try {
+
+            Class eventLeaseClass=UUT.loadClass("com.sun.jini.reggie.EventLease");
+            assertNull("loaded class was null", eventLeaseClass);
+            fail("Really shouldn't have gotten to here!");
+        } catch(Exception ex) {
+            
+        }
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/codebase/SocketAddressTest.java b/river-container-core/src/test/java/org/apache/river/container/codebase/SocketAddressTest.java
new file mode 100644
index 0000000..b5ba881
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/codebase/SocketAddressTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+
+ */
+package org.apache.river.container.codebase;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class SocketAddressTest {
+
+    public SocketAddressTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+    Exploratory test to find out how we can get a usable ip address for
+    use in the codebase annotation.
+    @throws Exception 
+    */
+    @Test
+    public void testSocketBind() throws Exception {
+
+        ServerSocket socket = new ServerSocket();
+        socket.bind(new InetSocketAddress(8080));
+        String socketName = InetAddress.getLocalHost().getCanonicalHostName();
+        assertEquals("socket name", "localhost:8080", socketName);
+    }
+
+    @Test
+    public void testInterfaces() throws Exception {
+        
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/config/ConfigurationParserTest.java b/river-container-core/src/test/java/org/apache/river/container/config/ConfigurationParserTest.java
new file mode 100644
index 0000000..94b0ed7
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/config/ConfigurationParserTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.config;
+
+import java.io.InputStream;
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBException;
+import org.junit.After;
+import org.junit.AfterClass;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ConfigurationParserTest {
+
+    public ConfigurationParserTest() throws Exception {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() throws JAXBException, SAXException {
+        ctx = JAXBContext.newInstance("org.apache.river.container.config");
+        um = ctx.createUnmarshaller();
+        SchemaFactory sf=SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        Source source=new StreamSource(getClass().getResourceAsStream("/schemas/config.xsd"));
+        Schema schema=sf.newSchema(source);
+        um.setSchema(schema);
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    JAXBContext ctx = null;
+    Unmarshaller um = null;
+
+    @Test
+    public void testConfigFileRead() throws Exception {
+
+        InputStream is = getClass().getResourceAsStream("config-test-doc.xml");
+        ContainerConfig containerConfig = (ContainerConfig) um.unmarshal(is);
+
+        assertEquals("lib/abc.jar", containerConfig.getClasspath().get(0).getValue());
+        assertEquals("lib/def.jar", containerConfig.getClasspath().get(1).getValue());
+        assertEquals("a", containerConfig.getClasspath().get(1).getParent());
+        assertEquals(3, containerConfig.getElements().size());
+    }
+
+    @Test
+    public void testInvalidConfigFileRead() {
+        InputStream is = getClass().getResourceAsStream("config-test-bad-doc.xml");
+        try {
+            ContainerConfig containerConfig = (ContainerConfig) um.unmarshal(is);
+            fail("Should have gotten a validation error.");
+        } catch (JAXBException ex) {
+            // Got the exception; all is good.
+        }
+   }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/deployer/DeployerConfigParserTest.java b/river-container-core/src/test/java/org/apache/river/container/deployer/DeployerConfigParserTest.java
new file mode 100644
index 0000000..d017b26
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/deployer/DeployerConfigParserTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.deployer;
+
+import java.util.List;
+import java.util.logging.Logger;
+import java.io.InputStream;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+
+ @author trasukg
+ */
+public class DeployerConfigParserTest {
+
+    private static final Logger log = Logger.getLogger(DeployerConfigParserTest.class.getName());
+
+    public DeployerConfigParserTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    /**
+     Ensure that the parsing basically happens; we can create the stream and run
+     it through the parser without errors.
+     */
+    public void testBasicParsing() throws ParseException {
+        ASTconfig config = parseTestConfig();
+        log.fine("grants string is:" + config.toString());
+        String expected = "config (grant (permission java.io.FilePermission \"${serviceArchive}\" \"read\") "
+                + "(permission java.net.SocketPermission \"*\" \"connect\")) "
+                + "(classloader (parent systemClassLoader) "
+                + "(jars (classpath (cpEntry commons-vfs-1.0.jar) "
+                + "(cpEntry commons-logging-1.1.1.jar) (cpEntry jsk-platform.jar) "
+                + "(cpEntry jsk-lib.jar) (cpEntry jsk-resources.jar) "
+                + "(cpEntry RiverSurrogate.jar "
+                + "org.apache.river.container.liaison.Strings "
+                + "org.apache.river.container.liaison.VirtualFileSystemConfiguration "
+                + "org.apache.river.container.liaison.VirtualFileSystemConfiguration$MyConfigurationFile "
+                + "\"META-INF/services/*\"))) (codebase jsk-dl.jar)) (configuration "
+                + "(configEntry discoveryGroup defaultDiscoveryGroup))";
+        assertEquals(expected, config.toString());
+    }
+
+    private ASTconfig parseTestConfig() throws ParseException {
+        InputStream in =
+                DeployerConfigParserTest.class.getResourceAsStream("sample.config");
+        assertTrue("No sample.config file found!", in != null);
+        ASTconfig config = DeployerConfigParser.parseConfig(in);
+        return config;
+    }
+
+    /**
+     Matching the ASTConfig should return the root node.
+
+     @throws Exception
+     */
+    @Test
+    public void testPathMatch() throws Exception {
+        ASTNode config = parseTestConfig();
+        List<ASTNode> matches = config.search(new Class[]{ASTconfig.class});
+        assertEquals("Length of match list", 1, matches.size());
+        assertEquals("matched node", config, matches.get(0));
+    }
+
+    /**
+     Matching the ASTConfig should return the root node.
+
+     @throws Exception
+     */
+    @Test
+    public void testlongerPathMatch() throws Exception {
+        ASTNode config = parseTestConfig();
+        List<ASTNode> matches = config.search(
+                new Class[]{ASTconfig.class, ASTclassloader.class,
+                    ASTjars.class});
+        assertEquals("Length of match list", 1, matches.size());
+        assertEquals("matched node class", ASTjars.class, matches.get(0).getClass());
+    }
+
+    /**
+     Checking format and contents of the permission grants.
+     */
+    @Test
+    public void testPermissionContents() throws Exception {
+        ASTNode config = parseTestConfig();
+        List<ASTNode> permNodes = config.search(
+                new Class[]{ASTconfig.class, ASTgrant.class, ASTpermission.class});
+        assertEquals("Number of permission nodes", 2, permNodes.size());
+        ASTpermission firstNode = (ASTpermission) permNodes.get(0);
+        assertEquals("permission java.io.FilePermission \"${serviceArchive}\" \"read\"", firstNode.toString());
+        assertEquals("children of permission node", 3, firstNode.jjtGetNumChildren());
+        assertEquals("Permission type for first node", "java.io.FilePermission",
+                ((ASTsymbol) (firstNode.jjtGetChild(0))).getValue());
+    }
+
+    @Test
+    public void testParentLoaderName() throws Exception {
+
+        ASTNode configNode = parseTestConfig();
+        String parentLoaderName = configNode.search(
+                new Class[]{ASTconfig.class, ASTclassloader.class, ASTparent.class}).get(0).jjtGetChild(0).toString();
+        assertEquals("parentLoaderName", "systemClassLoader", parentLoaderName);
+
+
+    }
+
+    @Test
+    public void testCodebaseNode() throws Exception {
+        ASTNode configNode = parseTestConfig();
+        ASTcodebase codebaseNode = (ASTcodebase) configNode.search(new Class[]{
+                    ASTconfig.class, ASTclassloader.class, ASTcodebase.class
+                }).get(0);
+        assertEquals("codebase callout", "jsk-dl.jar", codebaseNode.jjtGetChild(0).toString());
+    }
+}
\ No newline at end of file
diff --git a/river-container-core/src/test/java/org/apache/river/container/el/ArgParserTest.java b/river-container-core/src/test/java/org/apache/river/container/el/ArgParserTest.java
new file mode 100644
index 0000000..353357e
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/el/ArgParserTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.el;
+
+import org.apache.river.container.Utils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ArgParserTest {
+
+    public ArgParserTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+    ArgsParserImpl UUT = new ArgsParserImpl();
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testSimpleLine() {
+        String input = "A B C";
+        String[] expected = {"A", "B", "C"};
+
+        String[] actual = UUT.toArgs(input);
+
+        checkStringArray(expected, actual);
+    }
+
+    private void checkStringArray(String[] expected, String[] actual) {
+        boolean fail = false;
+        if (actual.length != expected.length) {
+            fail = true;
+        }
+
+        for (int i = 0; fail == false && i < expected.length; i++) {
+            if (expected[i] == null) {
+                fail = actual[i] == null;
+                continue;
+            }
+            fail = !expected[i].equals(actual[i]);
+        }
+        if (fail) {
+            fail("Expected " + Utils.format(expected) + ", got " + Utils.format(actual));
+            
+        }
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java b/river-container-core/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java
new file mode 100644
index 0000000..8254fb3
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java
@@ -0,0 +1,38 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.river.container.hsm;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ *
+ * @author trasukg
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({LoggingTest.class, ReturnTypeTest.class, InitializedMachineTest.class, StateMachineCompilerTest.class, PlainMachineExecutorTest.class})
+public class HSMTestSuite {
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+    
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java b/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java
new file mode 100644
index 0000000..47149fe
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java
@@ -0,0 +1,101 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.hsm;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.junit.After;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import static org.junit.Assert.*;

+

+/**

+ *

+ * @author trasukg

+ */

+public class InitializedMachineTest {

+

+    public InitializedMachineTest() {

+    }

+

+    @BeforeClass

+    public static void setUpClass() {

+        Logger.getLogger("org.apache.river.container.hsm").setLevel(Level.FINEST);

+    }

+

+    @AfterClass

+    public static void tearDownClass() {

+    }

+

+    @Before

+    public void setUp() throws Exception {

+        UUT=new InitializedTestSM();

+        UUTI=(InitializedTestSMInterface) PlainStateMachineExecutor.createProxy(UUT);

+    }

+

+    @After

+    public void tearDown() {

+    }

+

+    InitializedTestSMInterface UUTI=null;

+    InitializedTestSM UUT=null;

+    

+    @Test(expected=IllegalStateException.class)

+    public void testLockedException() throws Exception {

+        UUTI.setValue(20);

+    }

+    

+    /**

+     * If we transition to unlocked, then that means the @Transition tag

+     * was interpreted and executed correctly.

+     */

+    @Test

+    public void testUnlocking() {

+        UUTI.unlock();

+        assertTrue("lockedState is not instance of Unlocked", UUT.lockedState instanceof InitializedTestSM.Unlocked);

+        UUTI.setValue(20);

+    }

+    

+    /**

+     * The "Armed" state subclasses Locked, so the unlocking should continue to

+     * work.

+     */

+    @Test

+    public void testArming() {

+        UUTI.arm();

+        assertTrue("lockedState is not instance of Armed", UUT.lockedState instanceof InitializedTestSM.Armed);

+        

+        UUTI.unlock();

+        assertTrue("lockedState is not instance of Unlocked", UUT.lockedState instanceof InitializedTestSM.Unlocked);

+        UUTI.setValue(20);

+        

+    }

+    

+    /**

+     * Test that the methods are executing against the same instance that we

+     * created.

+     */

+    @Test

+    public void testSameInstance() {

+        UUTI.unlock();

+        UUTI.setValue(20);

+        assertEquals("Value through local instance", 20, UUT.getValue());

+    }

+}

diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java b/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java
new file mode 100644
index 0000000..4a3a80a
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java
@@ -0,0 +1,67 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.hsm;

+

+/**

+ * State machine to test whether the state machine correctly uses the supplied

+ * root state object.

+ */

+@RootState(InitializedTestSMInterface.class)

+public class InitializedTestSM {

+

+    private int value = 0;

+

+    @State({Locked.class, Unlocked.class, Armed.class})

+    @Initial(Locked.class)

+    Object lockedState;

+

+    public int getValue() {

+        return value;

+    }

+

+    public class Locked {

+

+        @Transition(Unlocked.class)

+        public void unlock() {

+            System.out.println("Locked.unlock()");

+        }

+

+        public void setValue(int v) {

+            throw new IllegalStateException("Locked!");

+        }

+

+        @Transition(Armed.class)

+        public void arm() {

+        }

+    }

+

+    public class Armed extends Locked {

+    }

+

+    public class Unlocked {

+

+        @Transition(Locked.class)

+        public void lock() {

+            System.out.println("Unlocked.lock()");

+        }

+

+        public void setValue(int v) {

+            value = v;

+        }

+    }

+}

diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java b/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java
new file mode 100644
index 0000000..a1c50a5
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java
@@ -0,0 +1,34 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.hsm;

+

+/**

+ *

+ * Interface for test of initialized state machine.

+ */

+public interface InitializedTestSMInterface {

+    public void lock();

+    

+    public void unlock();

+    

+    public void arm();

+    

+    public void setValue(int x);

+    

+    public int getValue();

+}

diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/LoggingTest.java b/river-container-core/src/test/java/org/apache/river/container/hsm/LoggingTest.java
new file mode 100644
index 0000000..78d1c9f
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/LoggingTest.java
@@ -0,0 +1,58 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.river.container.hsm;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class LoggingTest {
+
+    Logger log=Logger.getLogger(LoggingTest.class.getName());
+    
+    public LoggingTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger.getLogger("org.apache.river.container.hsm").setLevel(Level.FINER);
+        System.setProperty("java.util.logging.ConsoleHandler.level", "FINER");
+    }
+   
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+    
+    @Test
+    public void testLogging() {
+        System.out.println("Should be seeing some logging...");
+        
+        
+        log.log(Level.FINEST, "Finest");
+        log.log(Level.FINER, "Finer");
+        log.log(Level.FINE, "Fine");
+        log.log(Level.INFO, "Info");
+        log.log(Level.WARNING, "Warning");
+        log.log(Level.SEVERE, "Severe");
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java b/river-container-core/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java
new file mode 100644
index 0000000..ff5eae0
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.util.List;
+import java.lang.reflect.InvocationTargetException;
+import java.util.logging.Logger;
+import java.lang.reflect.Field;
+import java.util.logging.Level;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ * @author trasukg
+ */
+public class PlainMachineExecutorTest {
+
+    private static final Logger log =
+            Logger.getLogger(PlainMachineExecutorTest.class.getName());
+
+    public PlainMachineExecutorTest() throws InstantiationException, IllegalAccessException,
+            NoSuchMethodException, InvocationTargetException {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger.getLogger("org.apache.river.container.hsm").setLevel(Level.FINER);
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+    TestSMInterface UUT = (TestSMInterface) PlainStateMachineExecutor.createProxy(TestSM.class);
+
+    @Test
+    /**
+     * Verify that the list of active states is correct at beginning.
+     *
+     */
+    public void testActiveStates() {
+
+        assertEquals(3, UUT.getActiveStates().size());
+        assertTrue("activeStates should contain root state.",
+                UUT.getActiveStates().contains(TestSM.class));
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertTrue("activeStates should contain A1.",
+                UUT.getActiveStates().contains(TestSM.A.A1.class));
+    }
+
+    @Test
+    /**
+     * Test that the top-level (state-invariant) sayHelloConstant() method
+     * returns the correct value. Verifies that the top-level proxy is
+     * functioning correctly.
+     */
+    public void testStateMachine() throws InstantiationException, IllegalAccessException {
+        assertEquals("Hello", UUT.sayConstantHello());
+    }
+
+    @Test
+    /**
+     * Test that the top-level (state-invariant) sayHelloConstant() method
+     * returns the correct value. Verifies that the top-level proxy is
+     * functioning correctly.
+     */
+    public void testNullReturn() throws InstantiationException, IllegalAccessException {
+        assertEquals(null, UUT.returnNull());
+    }
+
+    @Test
+    /**
+     * <p> Verify that transitions from state A to B work, and that the
+     * behaviour varies with state. </p>
+     *
+     * <p> First call to sayHello() should return "Hello", second call should
+     * return "There". </p>
+     *
+     */
+    public void testSimpleTransition() throws InstantiationException, IllegalAccessException {
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        log.info("\n\nCalling hello()\n\n");
+        assertEquals("Hello", UUT.sayHello());
+        log.info("\n\n...done\n\n");
+        List<Class> activeStates = UUT.getActiveStates();
+        assertTrue("activeStates should contain B after transition.",
+                activeStates.contains(TestSM.B.class));
+        log.info("TestSM.B appears to have been active.");
+        assertFalse("activeStates should not contain A after transition.",
+                activeStates.contains(TestSM.A.class));
+        assertEquals("There", UUT.sayHello());
+    }
+
+    @Test
+    /**
+     * When we enter a state, the entry method should be called.
+     */
+    public void testEntryMethodExecution() {
+        assertEquals(1, UUT.getAEntryCount());
+        UUT.sayHello();
+        assertEquals(1, UUT.getAExitCount());
+    }
+
+    @Test
+    /**
+     * When we transition to a state but we are already in that state, the
+     * onEntry method should not be run.
+     */
+    public void testNullTransition() {
+        UUT.nullTransition();
+        UUT.nullTransition();
+        UUT.nullTransition();
+        assertEquals(1, UUT.getNullTransitionEntryCount());
+    }
+
+    @Test
+    /**
+     * Make sure that the gotoA() and gotoB() methods cause the appropriate
+     * transitions.
+     */
+    public void testABTransitions() {
+        UUT.gotoA();
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertFalse("activeStates should not contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+        UUT.gotoB();
+
+        assertFalse("activeStates should not contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertTrue("activeStates should contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+
+        UUT.gotoA();
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertFalse("activeStates should not contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+    }
+
+    @Test
+    /**
+     * @Retained annotations should be respected, and states should be
+     * initialized on entry if the
+     * @Retained is not there.
+     */
+    public void testStateInitialization() {
+        UUT.gotoA();
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertFalse("activeStates should not contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+        UUT.gotoB();
+
+        assertFalse("activeStates should not contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertTrue("activeStates should contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+        assertTrue("activeStates should contain B1.",
+                UUT.getActiveStates().contains(TestSM.B1.class));
+
+        UUT.moveSubstateOfB();
+        assertTrue("activeStates should contain B2.",
+                UUT.getActiveStates().contains(TestSM.B2.class));
+        UUT.gotoA();
+        /* the substate isn't marked @Retained, so should reset to initial
+         on gotoB().
+         */
+        UUT.gotoB();
+        assertTrue("activeStates should contain B1.",
+                UUT.getActiveStates().contains(TestSM.B1.class));
+
+
+    }
+
+    @Test
+    /**
+     * <p> Verify that the second interface is on the proxy and effective. </p>
+     *
+     * <p> After call to doSecondInterfaceAction(), call to sayHello() should
+     * return "HelloFromC". </p>
+     *
+     */
+    public void testSecondInterface() throws InstantiationException, IllegalAccessException {
+        log.info("\n\nCalling doSecondInterfaceAction()\n\n");
+        TestSMSecondInterface UUT2=(TestSMSecondInterface) UUT;
+        UUT2.doSecondInterfaceAction();
+        log.info("\n\n...done\n\n");
+        List<Class> activeStates = UUT.getActiveStates();
+        assertTrue("activeStates should contain C after transition.",
+                activeStates.contains(TestSM.C.class));
+        log.info("TestSM.C appears to have been active.");
+        assertFalse("activeStates should not contain A after transition.",
+                activeStates.contains(TestSM.A.class));
+        assertEquals("HelloFromC", UUT.sayHello());
+    }
+    
+    
+    /**
+     * Calling an event method that isn't implemented in the current state
+     * should throw an IllegalStateException.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testUnimplementedMethod() {
+        UUT.unimplementedMethod();
+    }
+}
\ No newline at end of file
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/ReturnTypeTest.java b/river-container-core/src/test/java/org/apache/river/container/hsm/ReturnTypeTest.java
new file mode 100644
index 0000000..66a7ab0
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/ReturnTypeTest.java
@@ -0,0 +1,49 @@
+/*

+ * To change this template, choose Tools | Templates

+ * and open the template in the editor.

+ */

+package org.apache.river.container.hsm;

+

+import java.lang.reflect.Method;

+import org.junit.After;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import static org.junit.Assert.*;

+/**

+ *

+ * @author trasukg

+ */

+public class ReturnTypeTest {

+    

+    public ReturnTypeTest() {

+    }

+    

+    @BeforeClass

+    public static void setUpClass() {

+    }

+    

+    @AfterClass

+    public static void tearDownClass() {

+    }

+    

+    @Before

+    public void setUp() {

+    }

+    

+    @After

+    public void tearDown() {

+    }

+    

+    /**

+     * Expectation is that if a method is declared to return 'void', then the

+     * 'Method.getReturnType()' value should be null;

+     */

+    @Test

+    public void testVoidReturnType() throws Exception {

+        Method m=this.getClass().getMethod("testVoidReturnType", new Class[0]);

+        assertEquals("return type wasn't void", void.class, m.getReturnType());

+        

+    }

+}

diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/StateMachineCompilerTest.java b/river-container-core/src/test/java/org/apache/river/container/hsm/StateMachineCompilerTest.java
new file mode 100644
index 0000000..f6cbc0b
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/StateMachineCompilerTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Test the State Machine Compiler
+ *
+ */
+public class StateMachineCompilerTest {
+
+    public StateMachineCompilerTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+    StateMachineCompiler compiler = new StateMachineCompiler();
+
+    @Test
+    public void testCompileReturnsMetaState() throws Exception {
+        MetaState stateMachine = compiler.compile(TestSM.class);
+        assertTrue(stateMachine + " isn't a MetaState", stateMachine instanceof MetaState);
+    }
+
+    @Test
+    public void testSampleActiveStates() throws Exception {
+        MetaState stateMachine = compiler.compile(TestSM.class);
+        List<Class> activeStates = stateMachine.getActiveStates();
+        checkContains(activeStates, TestSM.class);
+        checkContains(activeStates, TestSM.A.class);
+        checkContains(activeStates, TestSM.A.A1.class);
+    }
+
+    /**
+     * MetaState for TestSM should have event methods for sayHello and
+     * nullTransition, but nothing else.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testEventMethods() throws Exception {
+        MetaState stateMachine = compiler.compile(TestSM.class);
+        Collection<Method> methods = stateMachine.eventMethods.keySet();
+        Method sayHello = TestSMInterface.class.getMethod("sayHello");
+        assertNotNull("Didn't find sayHello() method in interface", sayHello);
+        checkContains(methods, sayHello);
+    }
+
+    /**
+     * A method annotated with
+     *
+     * @Guard should be reflected by a guarded transition operation.
+     * @throws Exception
+     */
+    @Test
+    public void testGuardMethod() throws Exception {
+        MetaState rootState = compiler.compile(TestSM.class);
+        assertEquals("Number of guard methods on root metastate", 1, rootState.guardMethods.size());
+
+    }
+
+    private void checkContains(Collection<?> collection, Object requiredObject) {
+        assertTrue(collection + " doesn't include " + requiredObject, collection.contains(requiredObject));
+    }
+
+    /**
+     * A method annotated with
+     *
+     * @Entry should be reflected by an invoke operation in the metastate.
+     * @throws Exception
+     */
+    @Test
+    public void testEntryMethod() throws Exception {
+        MetaState rootState = compiler.compile(TestSM.class);
+        MetaState metaStateA = findMetaState(rootState, TestSM.A.class);
+        assertEquals("Count of onEntry methods for A", 1, metaStateA.entryMethods.size());
+    }
+
+    /**
+     * A method annotated with
+     *
+     * @Entry should be reflected by an invoke operation in the metastate.
+     * @throws Exception
+     */
+    @Test
+    public void testExitMethod() throws Exception {
+        MetaState rootState = compiler.compile(TestSM.class);
+        MetaState metaStateA = findMetaState(rootState, TestSM.A.class);
+        assertEquals("Count of onExit methods for A", 1, metaStateA.exitMethods.size());
+    }
+
+    MetaState findMetaState(MetaState metaState, Class stateClass) {
+        for (SubstateInfo ssi : metaState.substates) {
+            for (MetaState ms : ssi.getPossibleMetaStates()) {
+                if (ms.stateClass == stateClass) {
+                    return ms;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void testStructure() throws Exception {
+        MetaState rootState = compiler.compile(TestSM.class);
+        String expectedStructure = "TestSM(state(A(state(A1 ) B(state(B1 B2 B3 ) ) ";
+        String actualStructure = rootState.getStateStructure();
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/TestSM.java b/river-container-core/src/test/java/org/apache/river/container/hsm/TestSM.java
new file mode 100644
index 0000000..2bfeada
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/TestSM.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author trasukg
+ */
+@RootState({TestSMInterface.class, TestSMSecondInterface.class})
+public class TestSM {
+
+    @State({A.class, B.class, C.class})
+    @Initial(A.class)
+    @Retained
+    public Object state;
+    
+    /* Really shouldn't need to be public - we'll fix shortly. */
+    @Controller
+    public StateMachineInfo controller;
+    int nullTransitionEntryCount = 0;
+    int aEntryCount = 0, aExitCount = 0;
+
+    public Object returnNull() {
+        return null;
+    }
+    
+    public List<Class> getActiveStates() {
+        try {
+            return controller.getActiveStates();
+        } catch (IllegalArgumentException ex) {
+            Logger.getLogger(TestSM.class.getName()).log(Level.SEVERE, null, ex);
+            throw new RuntimeException(ex);
+        } 
+    }
+
+    @Transition(A.class)
+    public void gotoA() {
+        //controller.transition(A.class);
+    }
+
+    @Transition(B.class)
+    public void gotoB() {
+        //controller.transition(B.class);
+    }
+
+    @Transition(C.class)
+    public void doSecondInterfaceAction() { }
+    
+    public int getAEntryCount() {
+        return aEntryCount;
+    }
+
+    public int getAExitCount() {
+        return aExitCount;
+    }
+
+    public int getNullTransitionEntryCount() {
+        return nullTransitionEntryCount;
+    }
+
+    public String sayConstantHello() {
+        return "Hello";
+    }
+
+    @Guard(A.class)
+    public boolean beFalse() {
+        return false;
+    }
+    
+    public class A {
+
+        @State({A1.class})
+        @Initial(A1.class)
+        public Object state;
+        
+        @Transition(B.class)
+        public String sayHello() {
+            //controller.transition(B.class);
+            return "Hello";
+        }
+
+        @Transition(A.class)
+        public void nullTransition() {
+            //controller.transition(A.class);
+        }
+
+        @OnEntry
+        public void onEntry() {
+            aEntryCount++;
+            nullTransitionEntryCount++;
+        }
+
+        @OnExit
+        public void onExit() {
+            aExitCount++;
+        }
+        
+        public class A1 {}
+    }
+
+    public class B {
+
+        @State({B1.class, B2.class, B3.class})
+        @Initial(B1.class)
+        Object state;
+
+        public String sayHello() {
+            return "There";
+        }
+    }
+
+    public class B1 {
+
+        @Transition(B2.class)
+        public void moveSubstateOfB() {
+            
+        }
+    }
+
+    public class B2 {
+    }
+
+    public class B3 {
+    }
+    
+    public class C {
+        public String sayHello() {
+            return "HelloFromC";
+        }
+    }
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/TestSMInterface.java b/river-container-core/src/test/java/org/apache/river/container/hsm/TestSMInterface.java
new file mode 100644
index 0000000..639a74e
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/TestSMInterface.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.util.List;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface TestSMInterface {
+
+    public String sayHello();
+
+    public String sayConstantHello();
+
+    public Object returnNull();
+
+    public int getNullTransitionEntryCount();
+
+    public void nullTransition();
+
+    public int getAEntryCount();
+
+    public int getAExitCount();
+
+    public void moveSubstateOfB();
+
+    public void gotoA();
+    public void gotoB();
+
+    List<Class> getActiveStates();
+    
+    public void unimplementedMethod();
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/hsm/TestSMSecondInterface.java b/river-container-core/src/test/java/org/apache/river/container/hsm/TestSMSecondInterface.java
new file mode 100644
index 0000000..8f82ae1
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/hsm/TestSMSecondInterface.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.hsm;
+
+import java.util.List;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface TestSMSecondInterface {
+
+    public void doSecondInterfaceAction();
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/security/SecurityManagerTest.java b/river-container-core/src/test/java/org/apache/river/container/security/SecurityManagerTest.java
new file mode 100644
index 0000000..2810ffe
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/security/SecurityManagerTest.java
@@ -0,0 +1,85 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.security;

+

+import java.io.IOException;

+import java.io.InputStream;

+import org.junit.*;

+

+/**

+ * 

+ * In order to test the security infrastructure, we need to setup a secure

+ * environment and then see if certain activities are denied.

+ * 

+ * How to do this?

+ * 

+ * One option would be to setup a plain application in a "secured" environment

+ * and have that application try to check access for a permission it doesn't

+ * have.

+ * 

+ * Another option is to run a JUnit test inside the "secured" environment, and

+ * verify that the allowed operation passes and the disallowed operation fails.

+ * 

+ * So... we'll need some infrastructure to run JUnit test suites inside the

+ * container.

+ * 

+ * @author trasukg

+ */

+public class SecurityManagerTest {

+    

+    public SecurityManagerTest() {

+    }

+

+    @BeforeClass

+    public static void setUpClass() throws Exception {

+    }

+

+    @AfterClass

+    public static void tearDownClass() throws Exception {

+    }

+    

+    @Before

+    public void setUp() {

+    }

+    

+    @After

+    public void tearDown() {

+    }

+    

+    @Test

+    public void testSecuritySetup() throws IOException {

+        /* Design by Magic... */

+        /* Start the container. */

+        Process p=Runtime.getRuntime().exec("Run the container");

+        InputStream pOut=p.getInputStream();

+        waitForContainerStartupMessage(pOut);

+        /* Deploy the test app to the container.  Test app starts up and runs the

+         * JUnit tests.

+         */

+        /* Confirm that the JUnit test was run.  If at all possible, just get the

+         * Result of the JUnit tests.

+         */

+        /* Undeploy the test app. */

+        /* Shutdown the container. */

+        p.destroy();

+    }

+

+    private void waitForContainerStartupMessage(InputStream pOut) {

+        throw new UnsupportedOperationException("Not yet implemented");

+    }

+}

diff --git a/river-container-core/src/test/java/org/apache/river/container/work/BasicWorkManagerTest.java b/river-container-core/src/test/java/org/apache/river/container/work/BasicWorkManagerTest.java
new file mode 100644
index 0000000..03b492d
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/work/BasicWorkManagerTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.container.work;
+
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class BasicWorkManagerTest {
+    
+    public BasicWorkManagerTest() {
+    }
+
+    BasicWorkManager UUT=new BasicWorkManager();
+    
+    @Test
+    public void testNullContextClassLoader() {
+        Harness h=new Harness();
+        UUT.queueTask(null, h);
+        waitHarness(4000,h);
+        assertEquals("Didn't use current context classloader.", 
+                Thread.currentThread().getContextClassLoader(), h.cl);
+    }
+    
+    @Test
+    public void testScheduledExecution() {
+        Harness h=new Harness();
+        long startTime=System.currentTimeMillis();
+        UUT.schedule(null, h, 2, TimeUnit.SECONDS);
+        waitHarness(4000,h);
+        assertEquals("Didn't use current context classloader.", 
+                Thread.currentThread().getContextClassLoader(), h.cl);
+        assertTrue("Delay was only " + (h.runTime - startTime),
+                h.runTime >= startTime+2000);
+    }
+    
+    private void waitHarness(long time, Harness h) {
+        long start=System.currentTimeMillis();
+        while ( !h.done && System.currentTimeMillis() - start < time) {
+            Thread.yield();
+        }
+        if (System.currentTimeMillis() - start >= time) {
+            fail("Harness task did not run.");
+        }
+    }
+    
+    private class Harness implements Runnable {
+        ClassLoader cl=null;
+        volatile boolean done=false;
+        String threadName=null;
+        volatile long runTime=0;
+        
+        @Override
+        public void run() {
+            cl=Thread.currentThread().getContextClassLoader();
+            threadName=Thread.currentThread().getName();
+            done=true;
+            runTime=System.currentTimeMillis();
+        }
+    }
+            
+}
diff --git a/river-container-core/src/test/java/org/apache/river/container/work/ContextualWorkManagerTest.java b/river-container-core/src/test/java/org/apache/river/container/work/ContextualWorkManagerTest.java
new file mode 100644
index 0000000..3c751d8
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/container/work/ContextualWorkManagerTest.java
@@ -0,0 +1,147 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.river.container.work;

+

+import java.util.ArrayList;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.apache.river.container.Strings;

+import static org.junit.Assert.*;

+import org.junit.Test;

+

+/**

+ *

+ * @author trasukg

+ */

+public class ContextualWorkManagerTest {

+

+    ContextualWorkManager UUT = new ContextualWorkManager();

+    WorkingContext context = UUT.createContext("Test-ctx");

+

+    @Test

+    public void testContextCreation() {

+        assertNotNull("context", context);

+        assertNotNull("context.workManager", context.getWorkManager());

+    }

+

+    @Test

+    public void testThreadCount() {

+        WorkerRunnable wt = new WorkerRunnable();

+        context.getWorkManager().queueTask(null, wt);

+        long start = System.currentTimeMillis();

+        while (System.currentTimeMillis() - start < 2000 & context.getActiveThreadCount() < 1) {

+            Thread.yield();

+        }

+        assertEquals("thread count", 1, context.getActiveThreadCount());

+        wt.proceed = true;

+    }

+

+    @Test

+    public void testChildThreadGroup() throws Exception {

+        WorkerRunnable wt = new WorkerRunnable();

+        context.getWorkManager().queueTask(null, wt);

+        long start = System.currentTimeMillis();

+        while (System.currentTimeMillis() - start < 2000 & context.getActiveThreadCount() < 1) {

+            Thread.yield();

+        }

+        Thread.sleep(1000); // Ugly wait for thread to start.

+        assertTrue("Thread group name '" + wt.getThreadGroupName() + "' doesn't start with ctx name",

+                wt.getThreadGroupName().startsWith("Test-ctx"));

+    }

+

+    /**

+     * Hold off on this -- not needed yet. *

+     */

+    @Test

+    public void testThreadCountWithChildren() throws Exception {

+        WorkerRunnable wt = new WorkerRunnable(2);

+        context.getWorkManager().queueTask(null, wt);

+        long start = System.currentTimeMillis();

+        while (System.currentTimeMillis() - start < 2000 & context.getActiveThreadCount() < 1) {

+            Thread.yield();

+        }

+        Thread.sleep(500);

+        try {

+            System.out.println("Checking thread count.");

+            assertEquals("thread count", 3, context.getActiveThreadCount());

+        } finally {

+            wt.proceed = true;

+            Thread.sleep(1000);

+        }

+    }

+

+    private class WorkerRunnable extends Thread {

+

+        String threadGroupName = Strings.UNKNOWN;

+        List<WorkerRunnable> children = new ArrayList<WorkerRunnable>();

+        String id = "--";

+        boolean proceed = false;

+        int nChildren = 0;

+

+        public WorkerRunnable() {

+        }

+

+        public String getThreadGroupName() {

+            return threadGroupName;

+        }

+

+        /**

+         * Hmm.. Is it possible that the thread group is assigned at thread

+         * creation time? Of course it is! Looks like the thread group id is

+         * assigned to the parent when the Thread instance is instantiated,

+         * rather than when the thread (i.e. the actual background thread) is

+         * launched.

+         *

+         * @param nChildren

+         */

+        public WorkerRunnable(int nChildren) {

+            this.nChildren = nChildren;

+        }

+

+        public void run() {

+            threadGroupName = Thread.currentThread().getThreadGroup().getName();

+

+            System.out.println("Worker " + id + " beginning in thread group "

+                    + Thread.currentThread().getThreadGroup().getName() + ".");

+            if (nChildren != 0) {

+                for (int x = 0; x < nChildren; x++) {

+                    WorkerRunnable newWorker = new WorkerRunnable();

+                    newWorker.id = "WorkerRunnable-" + (x + 1);

+                    children.add(newWorker);

+                }

+            }

+            try {

+                for (WorkerRunnable worker : children) {

+                    worker.start();

+                }

+                while (!proceed) {

+                    Thread.sleep(1500);

+                }

+            } catch (InterruptedException ex) {

+                Logger.getLogger(ContextualWorkManagerTest.class.getName()).log(Level.SEVERE, null, ex);

+            } finally {

+                for (WorkerRunnable worker : children) {

+                    worker.proceed = true;

+                }

+                System.out.println("Worker " + id + " ended.");

+

+            }

+        }

+    }

+}

diff --git a/river-container-core/src/test/java/org/apache/river/surrogate/SurrogateContextTest.java b/river-container-core/src/test/java/org/apache/river/surrogate/SurrogateContextTest.java
new file mode 100644
index 0000000..af67567
--- /dev/null
+++ b/river-container-core/src/test/java/org/apache/river/surrogate/SurrogateContextTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.surrogate;
+
+import java.io.File;
+import org.apache.river.container.deployer.ApplicationEnvironment;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class SurrogateContextTest {
+
+    ApplicationEnvironment UUT=null;
+
+    public SurrogateContextTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+        UUT=new ApplicationEnvironment();
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    /**
+     Make sure that the jar containing our test surrogate is actually there.
+     It should be built by the "-post-compile-test" target in 'build.xml'.
+     */
+    public void testThatTestJarIsPresent() {
+        System.out.println("Working dir is " + new File(".").getAbsolutePath());
+        File testJar=new File("../../build/test/files/sample-surrogate.jar");
+        assertTrue("No test jar present", testJar.exists());
+    }
+}
\ No newline at end of file
diff --git a/river-container-core/src/test/resources/org/apache/river/container/config/config-test-bad-doc.xml b/river-container-core/src/test/resources/org/apache/river/container/config/config-test-bad-doc.xml
new file mode 100644
index 0000000..cae5d9b
--- /dev/null
+++ b/river-container-core/src/test/resources/org/apache/river/container/config/config-test-bad-doc.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Document   : config-test-doc.xml
+    Created on : November 24, 2010, 4:13 PM
+    Author     : trasukg
+    Description:
+        Purpose of the document follows.
+-->
+
+<cfg:container-config  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+   xmlns:cfg='http://river.apache.org/xml/ns/container/config/1.0'
+   xsi:schemaLocation='http://river.apache.org/xml/ns/container/config/1.0 file:/home/trasukg/development/surrogate/schemas/config.xsd'>
+    <cfg:classpath>lib/abc.jar</cfg:classpath>
+
+    <cfg:discovery-context is-default="false" id="">
+        <cfg:locator></cfg:locator>
+        <cfg:group></cfg:group>
+    </cfg:discovery-context>
+
+    <cfg:property name="abc" value="def"/>
+
+    <!-- This ought to cause a validation error. -->
+    <component class="org.apache.SomethingOrOther"/>
+
+</cfg:container-config>
diff --git a/river-container-core/src/test/resources/org/apache/river/container/config/config-test-doc.xml b/river-container-core/src/test/resources/org/apache/river/container/config/config-test-doc.xml
new file mode 100644
index 0000000..c7af3fb
--- /dev/null
+++ b/river-container-core/src/test/resources/org/apache/river/container/config/config-test-doc.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Document   : config-test-doc.xml
+    Created on : November 24, 2010, 4:13 PM
+    Author     : trasukg
+    Description:
+        Purpose of the document follows.
+-->
+
+<cfg:container-config  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+   xmlns:cfg='http://river.apache.org/xml/ns/container/config/1.0'
+   xsi:schemaLocation='http://river.apache.org/xml/ns/container/config/1.0 file:/home/trasukg/development/surrogate/schemas/config.xsd'>
+    <cfg:classpath id="a">lib/abc.jar</cfg:classpath>
+    <cfg:classpath id="b" parent="a">lib/def.jar</cfg:classpath>
+
+    <cfg:discovery-context name="default">
+        <cfg:locator></cfg:locator>
+        <cfg:group></cfg:group>
+    </cfg:discovery-context>
+
+    <cfg:property name="abc" value="def"/>
+
+    <cfg:component class="org.apache.SomethingOrOther"/>
+
+</cfg:container-config>
diff --git a/river-container-core/src/test/resources/org/apache/river/container/deployer/sample.config b/river-container-core/src/test/resources/org/apache/river/container/deployer/sample.config
new file mode 100644
index 0000000..d7116d5
--- /dev/null
+++ b/river-container-core/src/test/resources/org/apache/river/container/deployer/sample.config
@@ -0,0 +1,54 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+// Sample file that our policy file parser should be able to process.

+

+// Grants given to all applications.

+grant {

+    java.io.FilePermission "${serviceArchive}" "read";

+    java.net.SocketPermission "*" "connect";

+}

+

+classloader {

+    // Variables required to set up the application classloader.

+    //For a privileged application deployer, parent=containerClassLoader;

+    parent systemClassLoader;

+

+    jars {

+        commons-vfs-1.0.jar,

+        commons-logging-1.1.1.jar,

+        jsk-platform.jar,

+        jsk-lib.jar,

+        jsk-resources.jar,

+        RiverSurrogate.jar(org.apache.river.container.liaison.Strings,

+            org.apache.river.container.liaison.VirtualFileSystemConfiguration, 

+            org.apache.river.container.liaison.VirtualFileSystemConfiguration$MyConfigurationFile, 

+            "META-INF/services/*")

+    }

+

+    codebase {jsk-dl.jar}

+}

+

+configuration {

+    // Anything on the left-hand side of '=' is set into the application config

+    // as a "special variable, accessible through '$name'.

+    discoveryGroup=defaultDiscoveryGroup;

+    

+    // For privileged deployer, include 

+    // context=context;

+}
\ No newline at end of file