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