BATCHEE-51 add stop and restart to SimpleRest api

Usage via:
* .../jbatch/rest/stop/${executionId}
* .../jbatch/rest/restart/${executionId}
diff --git a/gui/servlet/embedded/src/main/java/org/apache/batchee/servlet/SimpleRestController.java b/gui/servlet/embedded/src/main/java/org/apache/batchee/servlet/SimpleRestController.java
index 6f6d6dc..b86239f 100644
--- a/gui/servlet/embedded/src/main/java/org/apache/batchee/servlet/SimpleRestController.java
+++ b/gui/servlet/embedded/src/main/java/org/apache/batchee/servlet/SimpleRestController.java
@@ -22,6 +22,8 @@
 import java.util.Map;
 import java.util.Properties;
 
+import javax.batch.operations.JobExecutionAlreadyCompleteException;
+import javax.batch.operations.JobExecutionNotRunningException;
 import javax.batch.operations.JobOperator;
 import javax.batch.operations.JobStartException;
 import javax.batch.operations.NoSuchJobExecutionException;
@@ -38,6 +40,8 @@
 
     public static final String OP_START = "start/";
     public static final String OP_STATUS = "status/";
+    public static final String OP_STOP = "stop/";
+    public static final String OP_RESTART = "restart/";
 
     public static final long NO_JOB_ID = -1L;
 
@@ -58,29 +62,18 @@
             startBatch(path.substring(OP_START.length()), req, resp);
         } else if (path != null && path.startsWith(OP_STATUS)) {
             batchStatus(path.substring(OP_STATUS.length()), req, resp);
+        } else if (path != null && path.startsWith(OP_STOP)) {
+            batchStop(path.substring(OP_STOP.length()), req, resp);
+        } else if (path != null && path.startsWith(OP_RESTART)) {
+            batchRestart(path.substring(OP_RESTART.length()), req, resp);
         } else {
             unknownCommand(path, resp);
         }
     }
 
     private void startBatch(String batchName, HttpServletRequest req, HttpServletResponse resp) {
-        Properties jobProperties = new Properties();
-        Map<String, String[]> parameterMap = req.getParameterMap();
-        for (Map.Entry<String, String[]> paramEntry : parameterMap.entrySet()) {
-            String key = paramEntry.getKey();
-            if (key == null || key.length() == 0) {
-                reportFailure(NO_JOB_ID, resp, "Parameter key must be set");
-                return;
-            }
-
-            String[] vals = paramEntry.getValue();
-            if (vals == null || vals.length != 1) {
-                reportFailure(NO_JOB_ID, resp, "Exactly one value must be set for each parameter (parameter name=" + key + ")");
-                return;
-            }
-            String val = vals[0];
-            jobProperties.put(key, val);
-        }
+        Properties jobProperties = extractJobProperties(req, resp);
+        if (jobProperties == null) { return; }
 
         try {
             long jobId = jobOperator.start(batchName, jobProperties);
@@ -95,19 +88,11 @@
 
     private void batchStatus(String batchId, HttpServletRequest req, HttpServletResponse resp)
     {
-        if (batchId == null || batchId.isEmpty()) {
-            reportFailure(NO_JOB_ID, resp, "no executionId given");
+        Long executionId = extractExecutionId(batchId, resp);
+        if (executionId == null) {
             return;
         }
 
-        long executionId = NO_JOB_ID;
-
-        try {
-            executionId = Long.valueOf(batchId);
-        } catch(NumberFormatException nfe) {
-            reportFailure(NO_JOB_ID, resp, "executionId must be numeric, but is " + batchId);
-        }
-
         try {
             JobExecution jobExecution = jobOperator.getJobExecution(executionId);
             BatchStatus batchStatus = jobExecution.getBatchStatus();
@@ -115,16 +100,63 @@
         } catch (NoSuchJobExecutionException noSuchJob) {
             reportFailure(executionId, resp, "NoSuchJob");
         } catch (Exception generalException) {
-            StringBuilder msg = new StringBuilder("NoSuchJob");
+            StringBuilder msg = new StringBuilder("Failure in BatchExecution");
             appendExceptionMsg(msg, generalException);
             reportFailure(executionId, resp, msg.toString());
         }
     }
 
+    private void batchStop(String batchId, HttpServletRequest req, HttpServletResponse resp) {
+        Long executionId = extractExecutionId(batchId, resp);
+        if (executionId == null) {
+            return;
+        }
+
+        try {
+            jobOperator.stop(executionId);
+            reportSuccess(executionId, resp, BatchStatus.STOPPING.toString());
+        } catch (NoSuchJobExecutionException noSuchJob) {
+            reportFailure(executionId, resp, "NoSuchJob");
+        } catch (JobExecutionNotRunningException notRunningException) {
+            reportFailure(executionId, resp, "JobExecutionNotRunning");
+        } catch (Exception generalException) {
+            StringBuilder msg = new StringBuilder("Failure in BatchExecution");
+            appendExceptionMsg(msg, generalException);
+            reportFailure(executionId, resp, msg.toString());
+        }
+    }
+
+    private void batchRestart(String batchId, HttpServletRequest req, HttpServletResponse resp) {
+        Long executionId = extractExecutionId(batchId, resp);
+        if (executionId == null) {
+            return;
+        }
+
+        Properties jobProperties = extractJobProperties(req, resp);
+
+        try {
+            jobOperator.restart(executionId, jobProperties);
+        } catch (NoSuchJobExecutionException noSuchJob) {
+            reportFailure(executionId, resp, "NoSuchJob");
+        } catch (JobExecutionAlreadyCompleteException alreadyCompleted) {
+            reportFailure(executionId, resp, "NoSuchJob");
+        } catch (Exception generalException) {
+            StringBuilder msg = new StringBuilder("Failure in BatchExecution");
+            appendExceptionMsg(msg, generalException);
+            reportFailure(executionId, resp, msg.toString());
+        }
+    }
+
+
     private void unknownCommand(String path, HttpServletResponse resp) {
         StringBuilder msg = new StringBuilder("Unknown command:");
         msg.append(path).append('\n');
 
+        msg.append("The returned response if of MIME type text/plain and contains the following information\n");
+        msg.append("  {jobExecutionId} (or -1 if no executionId was detected)\\n\n");
+        msg.append("  OK (or FAILURE)\\n\n");
+        msg.append("  followed by command specific information\n");
+
         msg.append("\nKnown commands are:\n\n");
         msg.append("* ").append(OP_START).append(" - start a new batch job\n");
         msg.append("  Sample: http://localhost:8080/myapp/jbatch/rest/start/myjobname?param1=x&param2=y\n");
@@ -134,14 +166,52 @@
         msg.append("  Sample: http://localhost:8080/myapp/jbatch/rest/status/23\n");
         msg.append("  will return the state of executionId 23\n\n");
 
-        msg.append("The returned response if of MIME type text/plain and contains the following information\n");
-        msg.append("  {jobExecutionId} (or -1 if no executionId was detected)\\n\n");
-        msg.append("  OK (or FAILURE)\\n\n");
-        msg.append("  followed by command specific information\n");
+        msg.append("* ").append(OP_STOP).append(" - stop the job with the given executionId \n");
+        msg.append("  Sample: http://localhost:8080/myapp/jbatch/rest/stop/23\n");
+        msg.append("  will stop the job with executionId 23\n\n");
+
+        msg.append("* ").append(OP_RESTART).append(" - restart the job with the given executionId \n");
+        msg.append("  Sample: http://localhost:8080/myapp/jbatch/rest/restart/23\n");
+        msg.append("  will restart the job with executionId 23\n\n");
 
         reportFailure(NO_JOB_ID, resp, msg.toString());
     }
 
+    private Properties extractJobProperties(HttpServletRequest req, HttpServletResponse resp) {
+        Properties jobProperties = new Properties();
+        Map<String, String[]> parameterMap = req.getParameterMap();
+        for (Map.Entry<String, String[]> paramEntry : parameterMap.entrySet()) {
+            String key = paramEntry.getKey();
+            if (key == null || key.length() == 0) {
+                reportFailure(NO_JOB_ID, resp, "Parameter key must be set");
+                return null;
+            }
+
+            String[] vals = paramEntry.getValue();
+            if (vals == null || vals.length != 1) {
+                reportFailure(NO_JOB_ID, resp, "Exactly one value must be set for each parameter (parameter name=" + key + ")");
+                return null;
+            }
+            String val = vals[0];
+            jobProperties.put(key, val);
+        }
+        return jobProperties;
+    }
+
+    private Long extractExecutionId(String batchId, HttpServletResponse resp) {
+        if (batchId == null || batchId.isEmpty()) {
+            reportFailure(NO_JOB_ID, resp, "no executionId given");
+            return null;
+        }
+
+        try {
+            return Long.valueOf(batchId);
+        } catch(NumberFormatException nfe) {
+            reportFailure(NO_JOB_ID, resp, "executionId must be numeric, but is " + batchId);
+            return null;
+        }
+    }
+
     private void reportSuccess(long jobId, HttpServletResponse resp, String msg) {
         resp.setStatus(HttpServletResponse.SC_OK);
         writeContent(resp, Long.toString(jobId) + "\n");
diff --git a/gui/servlet/embedded/src/test/java/org/apache/batchee/servlet/ServletTest.java b/gui/servlet/embedded/src/test/java/org/apache/batchee/servlet/ServletTest.java
index 122b270..d6ed626 100644
--- a/gui/servlet/embedded/src/test/java/org/apache/batchee/servlet/ServletTest.java
+++ b/gui/servlet/embedded/src/test/java/org/apache/batchee/servlet/ServletTest.java
@@ -17,6 +17,7 @@
 package org.apache.batchee.servlet;
 
 import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.TextPage;
 import com.gargoylesoftware.htmlunit.WebClient;
 import com.gargoylesoftware.htmlunit.html.DomNode;
 import com.gargoylesoftware.htmlunit.html.HtmlPage;
@@ -40,12 +41,55 @@
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 @RunWith(Arquillian.class)
 public class ServletTest {
     @ArquillianResource
     private URL base;
 
+    @Deployment(testable = false)
+    public static Archive<?> war() {
+        final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "batchee-gui.war")
+                .addAsWebInfResource(new StringAsset(
+                        Descriptors.create(WebAppDescriptor.class)
+                                .metadataComplete(true)
+                                .createListener()
+                                .listenerClass(CreateSomeJobs.class.getName())
+                                .up()
+                                .createFilter()
+                                .filterName("JBatch Private Filter")
+                                .filterClass(JBatchServletInitializer.PrivateFilter.class.getName())
+                                .up()
+                                .createServlet()
+                                .servletName("JBatch")
+                                .servletClass(JBatchController.class.getName())
+                                .loadOnStartup(1)
+                                .up()
+                                .createFilterMapping()
+                                .filterName("JBatch Private Filter")
+                                .urlPattern("/*")
+                                .up()
+                                .createServletMapping()
+                                .servletName("JBatch")
+                                .urlPattern("/jbatch/*")
+                                .up()
+                                .exportAsString()), "web.xml")
+                        // GUI
+                .addPackages(true, JBatchController.class.getPackage())
+                        // test data to create some job things to do this test
+                .addPackage(CreateSomeJobs.class.getPackage())
+                .addAsWebInfResource("META-INF/batch-jobs/init.xml", "classes/META-INF/batch-jobs/init.xml");
+
+        for (final String resource : Arrays.asList("layout.jsp", "jobs.jsp", "job-instances.jsp", "step-executions.jsp",
+                "css/bootstrap.min.3.0.0.css", "js/bootstrap.min.3.0.0.js")) {
+            webArchive.addAsWebResource("META-INF/resources/internal/batchee/" + resource, "internal/batchee/" + resource);
+        }
+
+        return webArchive;
+    }
+
     @Test
     public void home() throws IOException {
         assertEquals("init", extractContent("", "/ul/li/a[1]/text()"));
@@ -67,6 +111,41 @@
         client.getPage(base.toExternalForm() + "jbatch/internal/batchee/jobs.jsp");
     }
 
+    @Test
+    public void testSimpleRest() throws IOException, InterruptedException {
+        String textContent = executeSimpleRest("start/init?value=OK&sleep=2");
+        Long executionId = extractExecutionId(textContent);
+
+        Thread.sleep(100L);
+
+        textContent = executeSimpleRest("status/" + executionId);
+        String[] parms = textContent.split("\n");
+        assertTrue(parms.length == 3);
+        assertEquals(BatchStatus.STARTED.toString(), parms[2]);
+    }
+
+    private String executeSimpleRest(String command) throws IOException {
+        final String startUrl = base.toExternalForm() + "jbatch/rest/" + command;
+        final WebClient webClient = newWebClient();
+        final TextPage page = webClient.getPage(startUrl);
+        String textContent = page.getContent();
+        assertNotNull(textContent);
+        assertTrue(textContent.contains("\nOK\n"));
+        extractExecutionId(textContent);
+
+        return textContent;
+    }
+
+    private Long extractExecutionId(String textContent) {
+        String[] parms = textContent.split("\n");
+        assertTrue(parms.length >= 2);
+        Long executionId = Long.valueOf(parms[0]);
+        assertTrue(-1L != executionId);
+
+        return executionId;
+    }
+
+
     private String extractContent(final String endUrl, final String xpath) throws IOException {
         final String url = base.toExternalForm() + "jbatch/" + endUrl;
         final WebClient webClient = newWebClient();
@@ -90,44 +169,5 @@
         return webClient;
     }
 
-    @Deployment(testable = false)
-    public static Archive<?> war() {
-        final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "batchee-gui.war")
-            .addAsWebInfResource(new StringAsset(
-                Descriptors.create(WebAppDescriptor.class)
-                    .metadataComplete(true)
-                    .createListener()
-                        .listenerClass(CreateSomeJobs.class.getName())
-                    .up()
-                    .createFilter()
-                        .filterName("JBatch Private Filter")
-                        .filterClass(JBatchServletInitializer.PrivateFilter.class.getName())
-                    .up()
-                    .createServlet()
-                        .servletName("JBatch")
-                        .servletClass(JBatchController.class.getName())
-                        .loadOnStartup(1)
-                    .up()
-                    .createFilterMapping()
-                        .filterName("JBatch Private Filter")
-                        .urlPattern("/*")
-                    .up()
-                    .createServletMapping()
-                        .servletName("JBatch")
-                        .urlPattern("/jbatch/*")
-                    .up()
-                    .exportAsString()), "web.xml")
-            // GUI
-            .addPackages(true, JBatchController.class.getPackage())
-            // test data to create some job things to do this test
-            .addPackage(CreateSomeJobs.class.getPackage())
-            .addAsWebInfResource("META-INF/batch-jobs/init.xml", "classes/META-INF/batch-jobs/init.xml");
 
-        for (final String resource : Arrays.asList("layout.jsp", "jobs.jsp", "job-instances.jsp", "step-executions.jsp",
-                                                    "css/bootstrap.min.3.0.0.css", "js/bootstrap.min.3.0.0.js")) {
-            webArchive.addAsWebResource("META-INF/resources/internal/batchee/" + resource, "internal/batchee/" + resource);
-        }
-
-        return webArchive;
-    }
 }