You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by gt...@apache.org on 2014/01/13 04:35:11 UTC

[04/40] Initial import of River Container project into git.

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java
----------------------------------------------------------------------
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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java
----------------------------------------------------------------------
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();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java
----------------------------------------------------------------------
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=" ";
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java
----------------------------------------------------------------------
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);
+    
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java
----------------------------------------------------------------------
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]);
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java
----------------------------------------------------------------------
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 {
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java
----------------------------------------------------------------------
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();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java
----------------------------------------------------------------------
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();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java
----------------------------------------------------------------------
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);
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java
----------------------------------------------------------------------
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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java
----------------------------------------------------------------------
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);
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java
----------------------------------------------------------------------
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";
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java
----------------------------------------------------------------------
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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java
----------------------------------------------------------------------
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);
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java
----------------------------------------------------------------------
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 {
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java
----------------------------------------------------------------------
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 {
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java
----------------------------------------------------------------------
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);
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
----------------------------------------------------------------------
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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java
----------------------------------------------------------------------
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 {
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java
----------------------------------------------------------------------
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();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/State.java
----------------------------------------------------------------------
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();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
----------------------------------------------------------------------
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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java
----------------------------------------------------------------------
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);
+    }
+}