OWB-1284 add a default main for CDI SE API
git-svn-id: https://svn.apache.org/repos/asf/openwebbeans/trunk@1857523 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webbeans-se/README.adoc b/webbeans-se/README.adoc
new file mode 100644
index 0000000..146584c
--- /dev/null
+++ b/webbeans-se/README.adoc
@@ -0,0 +1,71 @@
+= OpenWebBeans CDI SE implementation
+
+It is a plain CDI SE implementation but with a few specific features:
+
+. It enables to be extended through `org.apache.openwebbeans.se.SeContainerSelector`,
+. It has specific properties intended to be used to configured the container,
+. It provides a generic `main(String[])`
+
+== `SeContainer` OpenWebBeans properties
+
+[opts="header"]
+|===
+| Name | Description
+| `openwebbeans.disableDiscovery`|`true` to disable the automatic discovery, false otherwise
+| `openwebbeans.classes`|Comma separated values of fully qualified classes names to register in the container
+| `openwebbeans.packages`|Comma separated values of fully qualified packages (either direct packages if `package-info` exists or a class of this package if not) names to register in the container
+| `openwebbeans.packages.recursive`|Comma separated values of fully qualified packages (either direct packages if `package-info` exists or a class of this package if not) names to register in the container recursively
+| `openwebbeans.decorators`|List of decorators to enable
+| `openwebbeans.interceptors`|List of interceptors to enable
+| `openwebbeans.alternatives`|List of alternatives to enable
+| `openwebbeans.stereotypes`|List of alternative stereotypes to enable
+| `openwebbeans.properties`|Properties to enable - in `java.util.Properties` format
+| `openwebbeans.property.${key}`|A property to enable, this is not useful by itself in programmatic mode but `CDILauncher` can rely on it.
+|===
+
+== Generic Launcher
+
+`org.apache.openwebbeans.se.CDILauncher` is a reusable main to launch any CDI application.
+In other words you can launch. It reuses the previous part properties prefixed with `--`
+and getting their value using a space as separator.
+This main also takes a new property `--openwebbeans.main` to specify an optional
+main. The value is either a bean name or a fully qualified class name using `@Default` qualifier.
+The method executed is either `Runnable#run` if the bean implements it
+or a `run()` method or a `main()` method or a `main(String...)̀ method taking
+the cleaned up arguments.
+
+Example:
+
+[source,sh]
+----
+java -cp .... \
+ org.apache.openwebbeans.se.CDILauncher \
+ --openwebbeans.main myNamedMain \
+ --openwebbeans.disableDiscovery true \
+ --openwebbeans.packaged com.company.myapp
+ --cli-opt opt-value \
+ other thing
+----
+
+If the bean named `myNamedMain` is the following one:
+
+[source,java]
+----
+@Named("myNamedMain")
+@ApplicationScoped
+public class MyMain {
+ public void main(String... args) {
+ Stream.of(args).forEach(System.out::println);
+ }
+}
+----
+
+Then the execution will print:
+
+[source]
+----
+--cli-opt
+opt-value
+other
+thing
+----
diff --git a/webbeans-se/src/main/java/org/apache/openwebbeans/se/CDILauncher.java b/webbeans-se/src/main/java/org/apache/openwebbeans/se/CDILauncher.java
new file mode 100644
index 0000000..38ea857
--- /dev/null
+++ b/webbeans-se/src/main/java/org/apache/openwebbeans/se/CDILauncher.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.openwebbeans.se;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.se.SeContainer;
+import javax.enterprise.inject.se.SeContainerInitializer;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+
+public final class CDILauncher
+{
+ private CDILauncher()
+ {
+ // no-op
+ }
+
+ public static void main(final String[] args)
+ {
+ final SeContainerInitializer initializer = SeContainerInitializer.newInstance();
+ final Config config = configure(initializer, args);
+ try (final SeContainer container = initializer.initialize())
+ {
+ if (config.main != null)
+ {
+ executeMain(config, container);
+ }
+ // else the app can use other ways to execute code like @PreDestroy of a startup bean
+ }
+ }
+
+ private static void executeMain(final Config config, final SeContainer container)
+ {
+ final BeanManager manager = container.getBeanManager();
+ Set<Bean<?>> beans = manager.getBeans(config.main);
+ if (beans == null || beans.isEmpty())
+ {
+ try
+ {
+ beans = manager.getBeans(Thread.currentThread().getContextClassLoader().loadClass(config.main));
+ }
+ catch (final ClassNotFoundException e)
+ {
+ throw new IllegalArgumentException("No bean '" + config.main + "' found");
+ }
+ }
+ final Bean<?> bean = manager.resolve(beans);
+ final CreationalContext<Object> context = manager.createCreationalContext(null);
+ final Object reference = manager.getReference(bean, selectProxyType(bean.getTypes()), context);
+ try
+ {
+ if (Runnable.class.isInstance(reference))
+ {
+ Runnable.class.cast(reference).run();
+ }
+ else // by reflection
+ {
+ // try a main method taking parameters
+ // then just a main or run method
+ Method method = null;
+ try
+ {
+ method = bean.getBeanClass()
+ .getMethod("main", String[].class);
+ }
+ catch (final Exception e)
+ {
+ try
+ {
+ method = bean.getBeanClass()
+ .getMethod("main");
+ }
+ catch (final Exception e2)
+ {
+ try
+ {
+ method = bean.getBeanClass()
+ .getMethod("main");
+ }
+ catch (final Exception e3)
+ {
+ throw new IllegalArgumentException(
+ bean + " does not implements Runnable or has a public main method");
+ }
+ }
+ }
+ try
+ {
+ final Object output = method.invoke(
+ Modifier.isStatic(method.getModifiers()) ? null : reference,
+ method.getParameterCount() == 1 ? new Object[] { config.args } : new Object[0]);
+ if (output != null)
+ {
+ System.out.println(output);
+ }
+ }
+ catch (final IllegalAccessException e)
+ {
+ throw new IllegalStateException(e);
+ }
+ catch (final InvocationTargetException e)
+ {
+ throw new IllegalStateException(e.getTargetException());
+ }
+ }
+ }
+ finally
+ {
+ if (manager.isNormalScope(bean.getScope()))
+ {
+ context.release();
+ }
+ }
+ }
+
+ private static Class<?> selectProxyType(final Set<Type> types)
+ {
+ if (types.contains(Runnable.class))
+ {
+ return Runnable.class;
+ }
+ return Object.class;
+ }
+
+ private static Config configure(final SeContainerInitializer initializer, final String[] args)
+ {
+ String main = null;
+ final Collection<String> remaining = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++)
+ {
+ final String current = args[i];
+ if (current != null && current.startsWith("--openwebbeans."))
+ {
+ if (args.length <= i + 1)
+ {
+ throw new IllegalArgumentException("Missing argument value for: '" + args[i] + "'");
+ }
+ if (current.equals("--openwebbeans.main"))
+ {
+ if (main != null)
+ {
+ throw new IllegalArgumentException("Ambiguous main '" + main + "' vs '" + args[i + 1] + "'");
+ }
+ main = args[i + 1];
+ }
+ else
+ {
+ initializer.addProperty(current.substring("--".length()), args[i + 1]);
+ }
+ i++;
+ }
+ else
+ {
+ remaining.add(current);
+ }
+ }
+ return new Config(remaining.toArray(new String[0]), main);
+ }
+
+ private static class Config
+ {
+ private final String[] args;
+ private final String main;
+
+ private Config(final String[] args, final String main)
+ {
+ this.args = args;
+ this.main = main;
+ }
+ }
+}
diff --git a/webbeans-se/src/main/java/org/apache/openwebbeans/se/OWBInitializer.java b/webbeans-se/src/main/java/org/apache/openwebbeans/se/OWBInitializer.java
index 8101a29..7563658 100644
--- a/webbeans-se/src/main/java/org/apache/openwebbeans/se/OWBInitializer.java
+++ b/webbeans-se/src/main/java/org/apache/openwebbeans/se/OWBInitializer.java
@@ -31,6 +31,9 @@
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.enterprise.inject.spi.Extension;
+
+import java.io.IOException;
+import java.io.StringReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@@ -38,6 +41,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
+import java.util.function.Function;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
@@ -203,15 +207,69 @@
}
@Override
- public SeContainerInitializer addProperty(String key, Object value)
+ public SeContainerInitializer addProperty(final String key, final Object value)
{
- if (String.class.isInstance(value))
+ switch (key)
{
- properties.put(key, value);
- }
- else
- {
- services.put(key, value);
+ case "openwebbeans.disableDiscovery":
+ if ("true".equalsIgnoreCase(String.valueOf(value)))
+ {
+ disableDiscovery();
+ }
+ break;
+ case "openwebbeans.classes":
+ addBeanClasses(list(value, this::loadClass).toArray(Class[]::new));
+ break;
+ case "openwebbeans.interceptors":
+ enableInterceptors(list(value, this::loadClass).toArray(Class[]::new));
+ break;
+ case "openwebbeans.decorators":
+ enableDecorators(list(value, this::loadClass).toArray(Class[]::new));
+ break;
+ case "openwebbeans.alternatives":
+ selectAlternatives(list(value, this::loadClass).toArray(Class[]::new));
+ break;
+ case "openwebbeans.stereotypes":
+ selectAlternativeStereotypes(list(value, this::loadClass).toArray(Class[]::new));
+ break;
+ case "openwebbeans.extensions":
+ addExtensions((Class<? extends Extension>[]) list(value, this::loadClass)
+ .toArray(Class[]::new));
+ break;
+ case "openwebbeans.packages":
+ addPackages(list(value, this::loadPackage).toArray(Package[]::new));
+ break;
+ case "openwebbeans.packages.recursive":
+ addPackages(true, list(value, this::loadPackage).toArray(Package[]::new));
+ break;
+ case "openwebbeans.properties":
+ {
+ final Properties properties = new Properties();
+ try (final StringReader reader = new StringReader(String.valueOf(value)))
+ {
+ properties.load(reader);
+ }
+ catch (final IOException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ properties.stringPropertyNames().forEach(k -> addProperty(k, properties.getProperty(k)));
+ break;
+ }
+ case "openwebbeans.property.":
+ {
+ addProperty(key.substring("openwebbeans.property.".length()), value);
+ break;
+ }
+ default:
+ if (String.class.isInstance(value))
+ {
+ properties.put(key, value);
+ }
+ else
+ {
+ services.put(key, value);
+ }
}
return this;
}
@@ -237,4 +295,56 @@
scannerService.loader(loader);
return this;
}
+
+ private <T> Stream<T> list(final Object list, final Function<Object, T> mapper)
+ {
+ if (Collection.class.isInstance(list))
+ {
+ return Collection.class.cast(list).stream().map(mapper);
+ }
+ return Stream.of(String.valueOf(list).split(","))
+ .map(String::trim)
+ .filter(it -> !it.isEmpty())
+ .map(mapper);
+ }
+
+ private Package loadPackage(final Object obj)
+ {
+ if (Package.class.isInstance(obj))
+ {
+ return Package.class.cast(obj);
+ }
+ final String name = String.valueOf(obj);
+ try
+ {
+ return loader.loadClass(name + ".package-info").getPackage();
+ }
+ catch (final ClassNotFoundException e)
+ {
+ try
+ {
+ return loader.loadClass(name).getPackage();
+ }
+ catch (final ClassNotFoundException e1)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ }
+
+ private Class loadClass(final Object it)
+ {
+ if (Class.class.isInstance(it))
+ {
+ return Class.class.cast(it);
+ }
+ try
+ {
+ return loader.loadClass(String.valueOf(it));
+ }
+ catch (final ClassNotFoundException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
}
diff --git a/webbeans-se/src/test/java/org/apache/openwebbeans/se/CDILauncherTest.java b/webbeans-se/src/test/java/org/apache/openwebbeans/se/CDILauncherTest.java
new file mode 100644
index 0000000..6df3b2f
--- /dev/null
+++ b/webbeans-se/src/test/java/org/apache/openwebbeans/se/CDILauncherTest.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.openwebbeans.se;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Named;
+
+import org.junit.Test;
+
+public class CDILauncherTest
+{
+ @Test
+ public void namedRunnable()
+ {
+ assertFalse(MyRunnable.ran);
+ CDILauncher.main(new String[]{
+ "--openwebbeans.main", "main",
+ "--openwebbeans.disableDiscovery", "true",
+ "--openwebbeans.classes", MyRunnable.class.getName()
+ });
+ assertTrue(MyRunnable.ran);
+ }
+
+ @Test
+ public void typedRunnable()
+ {
+ assertFalse(MyRunnable2.ran);
+ CDILauncher.main(new String[]{
+ "--openwebbeans.main", MyRunnable2.class.getName(),
+ "--openwebbeans.disableDiscovery", "true",
+ "--openwebbeans.classes", MyRunnable2.class.getName()
+ });
+ assertTrue(MyRunnable2.ran);
+ }
+
+ @Test
+ public void mainArgs()
+ {
+ assertNull(MyMain.args);
+ CDILauncher.main(new String[]{
+ "--openwebbeans.main", MyMain.class.getName(),
+ "--openwebbeans.disableDiscovery", "true",
+ "--openwebbeans.classes", MyMain.class.getName(),
+ "--other", "yes",
+ "and", "args"
+ });
+ assertArrayEquals(new String[]{
+ "--other", "yes",
+ "and", "args"
+ }, MyMain.args);
+ }
+
+ @Named("main")
+ @ApplicationScoped
+ public static class MyRunnable implements Runnable
+ {
+ static boolean ran = false;
+
+ @Override
+ public void run()
+ {
+ ran = true;
+ }
+ }
+
+ @ApplicationScoped
+ public static class MyRunnable2 implements Runnable
+ {
+ static boolean ran = false;
+
+ @Override
+ public void run()
+ {
+ ran = true;
+ }
+ }
+
+ @ApplicationScoped
+ public static class MyMain
+ {
+ static String[] args;
+
+ public void main(final String... args)
+ {
+ MyMain.args = args;
+ }
+ }
+}