You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@batchee.apache.org by rm...@apache.org on 2014/01/04 20:56:48 UTC

git commit: BATCHEE-11 adding lifecycle handling + classloader building in cli

Updated Branches:
  refs/heads/master bdf3ae85e -> 0dd9d2c4d


BATCHEE-11 adding lifecycle handling + classloader building in cli


Project: http://git-wip-us.apache.org/repos/asf/incubator-batchee/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-batchee/commit/0dd9d2c4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-batchee/tree/0dd9d2c4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-batchee/diff/0dd9d2c4

Branch: refs/heads/master
Commit: 0dd9d2c4dabf8296a583225eb2581d57a03549cc
Parents: bdf3ae8
Author: Romain Manni-Bucau <rm...@apache.org>
Authored: Sat Jan 4 20:53:56 2014 +0100
Committer: Romain Manni-Bucau <rm...@apache.org>
Committed: Sat Jan 4 20:53:56 2014 +0100

----------------------------------------------------------------------
 .../container/services/ServicesManager.java     |   9 +-
 .../factory/CDIBatchArtifactFactory.java        |   3 +-
 .../factory/DefaultBatchArtifactFactory.java    |  14 +-
 tools/cli/pom.xml                               |  21 +++
 .../java/org/apache/batchee/cli/BatchEECLI.java |   3 +-
 .../org/apache/batchee/cli/command/Abandon.java |   2 +-
 .../apache/batchee/cli/command/Executions.java  |   2 +-
 .../apache/batchee/cli/command/Instances.java   |   2 +-
 .../batchee/cli/command/JobOperatorCommand.java | 156 +++++++++++++++++++
 .../org/apache/batchee/cli/command/Running.java |   2 +-
 .../batchee/cli/command/StartableCommand.java   |   2 +-
 .../org/apache/batchee/cli/command/Status.java  |   2 +-
 .../org/apache/batchee/cli/command/Stop.java    |   2 +-
 .../apache/batchee/cli/lifecycle/Lifecycle.java |  27 ++++
 .../cli/lifecycle/impl/CdiCtrlLifecycle.java    |  36 +++++
 .../lifecycle/impl/EJBContainerLifecycle.java   |  31 ++++
 .../cli/lifecycle/impl/LifecycleBase.java       |  51 ++++++
 .../cli/lifecycle/impl/SpringLifecycle.java     | 114 ++++++++++++++
 .../java/org/apache/batchee/cli/MainTest.java   |  23 +++
 19 files changed, 485 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/jbatch/src/main/java/org/apache/batchee/container/services/ServicesManager.java
----------------------------------------------------------------------
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/ServicesManager.java b/jbatch/src/main/java/org/apache/batchee/container/services/ServicesManager.java
index 8418127..892ae61 100755
--- a/jbatch/src/main/java/org/apache/batchee/container/services/ServicesManager.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/ServicesManager.java
@@ -163,13 +163,18 @@ public class ServicesManager implements BatchContainerConstants {
     }
 
     private <T extends BatchService> T loadService(final Class<T> serviceType) {
+        final Object existing = batchRuntimeConfig.get(serviceType.getName());
+        if (serviceType.isInstance(existing)) {
+            return serviceType.cast(existing);
+        }
+
         T service = null;
-        String className = batchRuntimeConfig.getProperty(serviceType.getSimpleName());
+        String className = batchRuntimeConfig.getProperty(serviceType.getSimpleName()); // short name first
         try {
             if (className != null) {
                 service = load(serviceType, className);
             } else {
-                className = batchRuntimeConfig.getProperty(serviceType.getName());
+                className = String.class.cast(existing);
                 if (className != null) {
                     service = load(serviceType, className);
                 }

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/jbatch/src/main/java/org/apache/batchee/container/services/factory/CDIBatchArtifactFactory.java
----------------------------------------------------------------------
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/factory/CDIBatchArtifactFactory.java b/jbatch/src/main/java/org/apache/batchee/container/services/factory/CDIBatchArtifactFactory.java
index 97b9983..c719430 100755
--- a/jbatch/src/main/java/org/apache/batchee/container/services/factory/CDIBatchArtifactFactory.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/factory/CDIBatchArtifactFactory.java
@@ -18,7 +18,6 @@ package org.apache.batchee.container.services.factory;
 
 import org.apache.batchee.container.cdi.BatchCDIInjectionExtension;
 import org.apache.batchee.container.exception.BatchContainerServiceException;
-import org.apache.batchee.spi.BatchArtifactFactory;
 
 import javax.enterprise.context.Dependent;
 import javax.enterprise.context.spi.CreationalContext;
@@ -29,7 +28,7 @@ import java.io.IOException;
 import java.util.Properties;
 import java.util.Set;
 
-public class CDIBatchArtifactFactory extends DefaultBatchArtifactFactory implements BatchArtifactFactory {
+public class CDIBatchArtifactFactory extends DefaultBatchArtifactFactory {
     @Override
     public Instance load(final String batchId) {
         try {

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/jbatch/src/main/java/org/apache/batchee/container/services/factory/DefaultBatchArtifactFactory.java
----------------------------------------------------------------------
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/factory/DefaultBatchArtifactFactory.java b/jbatch/src/main/java/org/apache/batchee/container/services/factory/DefaultBatchArtifactFactory.java
index 5171aa8..c477b4b 100755
--- a/jbatch/src/main/java/org/apache/batchee/container/services/factory/DefaultBatchArtifactFactory.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/factory/DefaultBatchArtifactFactory.java
@@ -47,7 +47,7 @@ public class DefaultBatchArtifactFactory implements BatchArtifactFactory, XMLStr
     @Override
     public Instance load(final String batchId) {
         final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
-        final ArtifactMap artifactMap = createArtifactsMap(tccl);
+        final ArtifactLocator artifactMap = createArtifactsLocator(tccl);
 
         Object loadedArtifact = artifactMap.getArtifactById(batchId);
         if (loadedArtifact == null) {
@@ -72,7 +72,7 @@ public class DefaultBatchArtifactFactory implements BatchArtifactFactory, XMLStr
         return new Instance(loadedArtifact, null);
     }
 
-    private ArtifactMap createArtifactsMap(final ClassLoader tccl) {
+    protected ArtifactLocator createArtifactsLocator(final ClassLoader tccl) {
         final ArtifactMap artifactMap = new ArtifactMap();
         initArtifactMapFromClassLoader(artifactMap, tccl, BATCH_XML);
         initArtifactMapFromClassLoader(artifactMap, tccl, BATCHEE_XML);
@@ -183,7 +183,11 @@ public class DefaultBatchArtifactFactory implements BatchArtifactFactory, XMLStr
         }
     }
 
-    private class ArtifactMap {
+    protected static interface ArtifactLocator {
+        Object getArtifactById(String id);
+    }
+
+    private class ArtifactMap implements ArtifactLocator {
         private Map<String, Class<?>> idToArtifactClassMap = new HashMap<String, Class<?>>();
 
         // Maps to a list of types not a single type since there's no reason a single artifact couldn't be annotated
@@ -219,7 +223,8 @@ public class DefaultBatchArtifactFactory implements BatchArtifactFactory, XMLStr
             }
         }
 
-        private Object getArtifactById(final String id) {
+        @Override
+        public Object getArtifactById(final String id) {
             Object artifactInstance = null;
 
             try {
@@ -241,6 +246,5 @@ public class DefaultBatchArtifactFactory implements BatchArtifactFactory, XMLStr
     @Override
     public void init(final Properties batchConfig) throws BatchContainerServiceException {
         // no-op
-
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/pom.xml
----------------------------------------------------------------------
diff --git a/tools/cli/pom.xml b/tools/cli/pom.xml
index 57384e7..098d20d 100644
--- a/tools/cli/pom.xml
+++ b/tools/cli/pom.xml
@@ -61,6 +61,27 @@
       <version>3.1</version>
     </dependency>
 
+    <!-- for lifecycles -->
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-ejb_3.1_spec</artifactId>
+      <version>1.0.2</version>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.deltaspike.cdictrl</groupId>
+      <artifactId>deltaspike-cdictrl-api</artifactId>
+      <version>0.5</version>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+      <version>4.0.0.RELEASE</version>
+      <optional>true</optional>
+    </dependency>
+
+
     <dependency>
       <groupId>org.testng</groupId>
       <artifactId>testng</artifactId>

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/BatchEECLI.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/BatchEECLI.java b/tools/cli/src/main/java/org/apache/batchee/cli/BatchEECLI.java
index b5e6dce..f472b26 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/BatchEECLI.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/BatchEECLI.java
@@ -42,7 +42,8 @@ public class BatchEECLI {
         final Cli<Runnable> parser = builder.build();
 
         try {
-            parser.parse(args).run();
+            final Runnable cmd = parser.parse(args);
+            cmd.run();
         } catch (final ParseException e) {
             parser.parse("help").run();
         }

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/Abandon.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/Abandon.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/Abandon.java
index 8c8e240..c4ea4f7 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/Abandon.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/Abandon.java
@@ -25,7 +25,7 @@ public class Abandon extends JobOperatorCommand {
     private long id;
 
     @Override
-    public void run() {
+    public void doRun() {
         operator().abandon(id);
         info("Abandonned batch " + id);
     }

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/Executions.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/Executions.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/Executions.java
index 3af4a04..321a49e 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/Executions.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/Executions.java
@@ -30,7 +30,7 @@ public class Executions extends JobOperatorCommand {
     private long id;
 
     @Override
-    public void run() {
+    public void doRun() {
         final List<JobExecution> executions = operator().getJobExecutions(new JobInstanceImpl(id));
         if (!executions.isEmpty()) {
             info("Executions of " + executions.iterator().next().getJobName() + " for instance " + id);

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/Instances.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/Instances.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/Instances.java
index c0e1c6d..f2ae55a 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/Instances.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/Instances.java
@@ -35,7 +35,7 @@ public class Instances extends JobOperatorCommand {
     private int count = 100;
 
     @Override
-    public void run() {
+    public void doRun() {
         final JobOperator operator = operator();
         final long total = operator.getJobInstanceCount(name);
         info(name + " has " + total + " job instances");

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/JobOperatorCommand.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/JobOperatorCommand.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/JobOperatorCommand.java
index feb62ad..3d4e621 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/JobOperatorCommand.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/JobOperatorCommand.java
@@ -17,6 +17,8 @@
 package org.apache.batchee.cli.command;
 
 import io.airlift.command.Option;
+import org.apache.batchee.cli.lifecycle.Lifecycle;
+import org.apache.batchee.container.exception.BatchContainerRuntimeException;
 import org.apache.batchee.jaxrs.client.BatchEEJAXRSClientFactory;
 import org.apache.batchee.jaxrs.client.ClientConfiguration;
 import org.apache.batchee.jaxrs.client.ClientSecurity;
@@ -24,7 +26,25 @@ import org.apache.batchee.jaxrs.client.ClientSslConfiguration;
 
 import javax.batch.operations.JobOperator;
 import javax.batch.runtime.BatchRuntime;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.LinkedList;
 
+import static java.lang.Thread.currentThread;
+
+/**
+ * base class handling:
+ * - classloader enriched with libs folders (and subfolders)
+ * - Lifecycle (allow to start/stop a container)
+ *
+ * Note: the classloader is created from libs command, it is handy to organize batches
+ *       by folders to be able to run them contextual using this command.
+*/
 public abstract class JobOperatorCommand implements Runnable {
     @Option(name = "-url", description = "when using JAXRS the batchee resource url")
     private String baseUrl = null;
@@ -68,6 +88,15 @@ public abstract class JobOperatorCommand implements Runnable {
     @Option(name = "-trustManagerProvider", description = "when using JAXRS the trustManagerProvider")
     private String trustManagerProvider = null;
 
+    @Option(name = "-lifecycle", description = "the lifecycle class to use")
+    private String lifecycle = null;
+
+    @Option(name = "-libs", description = "folder containing additional libraries, the folder is added too to the loader")
+    private String libs = null;
+
+    @Option(name = "-shared-libs", description = "folder containing shared libraries, the folder is added too to the loader")
+    private String sharedLibs = null;
+
     protected JobOperator operator() {
         if (baseUrl == null) {
             return BatchRuntime.getJobOperator();
@@ -101,4 +130,131 @@ public abstract class JobOperatorCommand implements Runnable {
     protected void info(final String text) {
         System.out.println(text);
     }
+
+    protected abstract void doRun();
+
+    @Override
+    public final void run() {
+        final ClassLoader oldLoader = currentThread().getContextClassLoader();
+        final ClassLoader loader;
+        try {
+            loader = createLoader(oldLoader);
+        } catch (final MalformedURLException e) {
+            throw new BatchContainerRuntimeException(e);
+        }
+
+        if (loader != oldLoader) {
+            currentThread().setContextClassLoader(loader);
+        }
+
+        try {
+            final Lifecycle<Object> lifecycleInstance;
+            final Object state;
+            if (lifecycle != null) {
+                lifecycleInstance = createLifecycle(loader);
+                state = lifecycleInstance.start();
+            } else {
+                lifecycleInstance = null;
+                state = null;
+            }
+
+            try {
+                doRun();
+            } finally {
+                if (lifecycleInstance != null) {
+                    lifecycleInstance.stop(state);
+                }
+            }
+        } finally {
+            currentThread().setContextClassLoader(oldLoader);
+        }
+    }
+
+    private Lifecycle<Object> createLifecycle(final ClassLoader loader) {
+        // some shortcuts are nicer to use from CLI
+        if ("ejbcontainer".equalsIgnoreCase(lifecycle)) {
+            lifecycle = "org.apache.batchee.cli.lifecycle.impl.EJBContainerLifecycle";
+        } else if ("cdi".equalsIgnoreCase(lifecycle)) {
+            lifecycle = "org.apache.batchee.cli.lifecycle.impl.CdiCtrlLifecycle";
+        } else if ("spring".equalsIgnoreCase(lifecycle)) {
+            lifecycle = "org.apache.batchee.cli.lifecycle.impl.SpringLifecycle";
+        }
+
+        try {
+            return (Lifecycle<Object>) loader.loadClass(lifecycle).newInstance();
+        } catch (final Exception e) {
+            throw new BatchContainerRuntimeException(e);
+        }
+    }
+
+    private ClassLoader createLoader(final ClassLoader parent) throws MalformedURLException {
+        if (libs == null) {
+            return parent;
+        }
+
+        final File folder = new File(libs);
+        if (!folder.exists()) {
+            return parent;
+        }
+
+        // we add libs/*.jar and libs/xxx/*.jar to be able to sort libs but only one level to keep it simple
+        final Collection<URL> urls = new LinkedList<URL>();
+        addFolder(folder, urls);
+        if (sharedLibs != null) { // add it later to let specific libs be taken before
+            addFolder(new File(sharedLibs), urls);
+        }
+
+        return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
+    }
+
+    private void addFolder(File folder, Collection<URL> urls) throws MalformedURLException {
+        if (!folder.exists()) {
+            return;
+        }
+
+        addJars(folder, urls);
+
+        final File[] subFolders = folder.listFiles(DirFilter.INSTANCE);
+        if (subFolders != null) {
+            for (final File f : subFolders) {
+                addJars(f, urls);
+            }
+        }
+    }
+
+    private static void addJars(final File folder, final Collection<URL> urls) throws MalformedURLException {
+        final File[] additionals = folder.listFiles(JarFilter.INSTANCE);
+        if (additionals != null) {
+            for (final File toAdd : additionals) {
+                urls.add(toAdd.toURI().toURL());
+            }
+        }
+        urls.add(folder.toURI().toURL());
+    }
+
+    private static class JarFilter implements FilenameFilter {
+        public static final FilenameFilter INSTANCE = new JarFilter();
+
+        private JarFilter() {
+            // no-op
+        }
+
+        @Override
+        public boolean accept(final File dir, final String name) {
+            return dir.isFile() && (name.endsWith(".jar") || name.endsWith(".zip"));
+        }
+    }
+
+    private static class DirFilter implements FileFilter {
+        public static final FileFilter INSTANCE = new DirFilter();
+
+        private DirFilter() {
+            // no-op
+        }
+
+        @Override
+        public boolean accept(final File dir) {
+            return dir.isDirectory() && !dir.getName().startsWith(".");
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/Running.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/Running.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/Running.java
index 6c5bddc..5ec4f15 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/Running.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/Running.java
@@ -30,7 +30,7 @@ import java.util.Set;
 @Command(name = "running", description = "list running batches")
 public class Running extends JobOperatorCommand {
     @Override
-    public void run() {
+    public void doRun() {
         final JobOperator operator = operator();
         final Set<String> names = operator.getJobNames();
         if (names == null || names.isEmpty()) {

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/StartableCommand.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/StartableCommand.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/StartableCommand.java
index ad7c92b..7440bc1 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/StartableCommand.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/StartableCommand.java
@@ -39,7 +39,7 @@ public abstract class StartableCommand extends JobOperatorCommand {
     protected List<String> properties;
 
     @Override
-    public void run() {
+    public void doRun() {
         final JobOperator operator = operator();
         final long id = doStart(operator);
         if (wait) {

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/Status.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/Status.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/Status.java
index 1b60a19..109cc73 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/Status.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/Status.java
@@ -29,7 +29,7 @@ import java.util.Set;
 @Command(name = "status", description = "list last batches statuses")
 public class Status extends JobOperatorCommand {
     @Override
-    public void run() {
+    public void doRun() {
         final JobOperator operator = operator();
         final Set<String> names = operator.getJobNames();
         if (names == null || names.isEmpty()) {

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/command/Stop.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/command/Stop.java b/tools/cli/src/main/java/org/apache/batchee/cli/command/Stop.java
index 80a9bec..c65d0f6 100644
--- a/tools/cli/src/main/java/org/apache/batchee/cli/command/Stop.java
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/command/Stop.java
@@ -25,7 +25,7 @@ public class Stop extends JobOperatorCommand {
     private long id;
 
     @Override
-    public void run() {
+    public void doRun() {
         operator().stop(id);
         info("Stopped batch " + id);
     }

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/Lifecycle.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/Lifecycle.java b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/Lifecycle.java
new file mode 100644
index 0000000..e27ed29
--- /dev/null
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/Lifecycle.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.batchee.cli.lifecycle;
+
+/**
+ * Handle before/after hooks for each command.
+ *
+ * @param <T> the state type
+ */
+public interface Lifecycle<T> {
+    T start();
+    void stop(T state);
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/CdiCtrlLifecycle.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/CdiCtrlLifecycle.java b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/CdiCtrlLifecycle.java
new file mode 100644
index 0000000..e688463
--- /dev/null
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/CdiCtrlLifecycle.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.batchee.cli.lifecycle.impl;
+
+import org.apache.deltaspike.cdise.api.CdiContainer;
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+
+public class CdiCtrlLifecycle extends LifecycleBase<CdiContainer> {
+    @Override
+    public CdiContainer start() {
+        final CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();
+        cdiContainer.boot(configuration("cdictrl"));
+        cdiContainer.getContextControl().startContexts();
+        return cdiContainer;
+    }
+
+    @Override
+    public void stop(final CdiContainer cdiContainer) {
+        cdiContainer.getContextControl().stopContexts();
+        cdiContainer.shutdown();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/EJBContainerLifecycle.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/EJBContainerLifecycle.java b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/EJBContainerLifecycle.java
new file mode 100644
index 0000000..c899567
--- /dev/null
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/EJBContainerLifecycle.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.batchee.cli.lifecycle.impl;
+
+import javax.ejb.embeddable.EJBContainer;
+
+public class EJBContainerLifecycle extends LifecycleBase<EJBContainer> {
+    @Override
+    public EJBContainer start() {
+        return EJBContainer.createEJBContainer(configuration("ejbcontainer"));
+    }
+
+    @Override
+    public void stop(final EJBContainer state) {
+        state.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/LifecycleBase.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/LifecycleBase.java b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/LifecycleBase.java
new file mode 100644
index 0000000..be7cf2d
--- /dev/null
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/LifecycleBase.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.batchee.cli.lifecycle.impl;
+
+import org.apache.batchee.cli.lifecycle.Lifecycle;
+import org.apache.batchee.container.exception.BatchContainerRuntimeException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+
+public abstract class LifecycleBase<T> implements Lifecycle<T> {
+    protected static final String LIFECYCLE_PROPERTIES_FILE = "batchee-lifecycle.properties";
+
+    protected Map<?, ?> configuration(final String name) {
+        final String propPath = System.getProperty("batchee.lifecycle." + name + ".properties-path", LIFECYCLE_PROPERTIES_FILE);
+
+        final Properties p = new Properties();
+        final File f = new File(propPath);
+        if (f.isFile()) {
+            try {
+                final InputStream is = new FileInputStream(f);
+                try {
+                    p.load(is);
+                } finally {
+                    is.close();
+                }
+            } catch (final Exception e) {
+                throw new BatchContainerRuntimeException(e);
+            }
+        }
+
+        return p;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/SpringLifecycle.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/SpringLifecycle.java b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/SpringLifecycle.java
new file mode 100644
index 0000000..ce4cc71
--- /dev/null
+++ b/tools/cli/src/main/java/org/apache/batchee/cli/lifecycle/impl/SpringLifecycle.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.batchee.cli.lifecycle.impl;
+
+import org.apache.batchee.container.exception.BatchContainerRuntimeException;
+import org.apache.batchee.container.services.ServicesManager;
+import org.apache.batchee.container.services.ServicesManagerLocator;
+import org.apache.batchee.container.services.factory.DefaultBatchArtifactFactory;
+import org.apache.batchee.spi.BatchArtifactFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Properties;
+
+import static java.lang.Thread.currentThread;
+import static java.util.Arrays.asList;
+
+public class SpringLifecycle extends LifecycleBase<AbstractApplicationContext> {
+    @Override
+    public AbstractApplicationContext start() {
+        final Map<?, ?> config = configuration("spring");
+
+        final AbstractApplicationContext ctx;
+        if (config.containsKey("locations")) {
+            final Collection<String> locations = new LinkedList<String>();
+            locations.addAll(asList(String.class.cast(config.get("locations")).split(",")));
+            ctx = new ClassPathXmlApplicationContext(locations.toArray(new String[locations.size()]));
+        } else if (config.containsKey("classes")) {
+            final Collection<Class<?>> classes = new LinkedList<Class<?>>();
+            final ClassLoader loader = currentThread().getContextClassLoader();
+            for (final String clazz : String.class.cast(config.get("classes")).split(",")) {
+                try {
+                    classes.add(loader.loadClass(clazz));
+                } catch (final ClassNotFoundException e) {
+                    throw new BatchContainerRuntimeException(e);
+                }
+            }
+            ctx = new AnnotationConfigApplicationContext(classes.toArray(new Class<?>[classes.size()]));
+        } else {
+            throw new IllegalArgumentException(LIFECYCLE_PROPERTIES_FILE + " should contain 'classes' or 'locations' key");
+        }
+
+        final Properties p = new Properties();
+        p.put(BatchArtifactFactory.class.getName(), new SpringArtifactFactory(ctx));
+
+        final ServicesManager mgr = new ServicesManager();
+        mgr.init(p);
+
+        ServicesManager.setServicesManagerLocator(new ServicesManagerLocator() {
+            @Override
+            public ServicesManager find() {
+                return mgr;
+            }
+        });
+
+        return ctx;
+    }
+
+    @Override
+    public void stop(final AbstractApplicationContext state) {
+        state.close();
+    }
+
+    public static class SpringArtifactFactory extends DefaultBatchArtifactFactory {
+        private final ApplicationContext context;
+
+        public SpringArtifactFactory(final ApplicationContext ctx) {
+            context = ctx;
+        }
+
+        @Override
+        protected ArtifactLocator createArtifactsLocator(final ClassLoader tccl) {
+            return new SpringArtifactLocator(super.createArtifactsLocator(tccl), context);
+        }
+
+        private static class SpringArtifactLocator implements ArtifactLocator {
+            private final ArtifactLocator parent;
+            private final ApplicationContext context;
+
+            public SpringArtifactLocator(final ArtifactLocator parent, final ApplicationContext context) {
+                this.parent = parent;
+                this.context = context;
+            }
+
+            @Override
+            public Object getArtifactById(final String id) {
+                final Object bean = context.getBean(id);
+                if (bean != null) {
+                    return bean;
+                }
+                return parent.getArtifactById(id);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/0dd9d2c4/tools/cli/src/test/java/org/apache/batchee/cli/MainTest.java
----------------------------------------------------------------------
diff --git a/tools/cli/src/test/java/org/apache/batchee/cli/MainTest.java b/tools/cli/src/test/java/org/apache/batchee/cli/MainTest.java
index acdc071..0d966ba 100644
--- a/tools/cli/src/test/java/org/apache/batchee/cli/MainTest.java
+++ b/tools/cli/src/test/java/org/apache/batchee/cli/MainTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.batchee.cli;
 
+import org.apache.batchee.cli.lifecycle.Lifecycle;
 import org.apache.batchee.util.Batches;
 import org.junit.Rule;
 import org.junit.Test;
@@ -26,6 +27,7 @@ import javax.batch.runtime.BatchRuntime;
 
 import static org.apache.batchee.cli.BatchEECLI.main;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 public class MainTest {
@@ -150,4 +152,25 @@ public class MainTest {
 
         Batches.waitForEnd(jobOperator, id);
     }
+
+    @Test
+    public void lifecycle() {
+        // whatever child of JobOperatorCommand so using running which is simple
+        main(new String[]{ "running", "-lifecycle", MyLifecycle.class.getName() });
+        assertEquals("start stop", MyLifecycle.result);
+    }
+
+    public static class MyLifecycle implements Lifecycle<String> {
+        private static String result;
+
+        @Override
+        public String start() {
+            return "start";
+        }
+
+        @Override
+        public void stop(final String state) {
+            result = state + " stop";
+        }
+    }
 }