getProperties() {
+ return properties;
+ }
+
+ @Override
+ public String toString() {
+ String coordinates = taskGroupId + ":" + artifact + ":" + taskVersion ;
+ if(StringUtils.hasText(taskClassifier)){
+ coordinates = coordinates + ":" + taskClassifier;
+ }
+ coordinates = coordinates + ":" + taskExtension;
+ return coordinates;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o){
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()){
+ return false;
+ }
+
+ TaskLaunchRequest that = (TaskLaunchRequest) o;
+
+ if (!artifact.equals(that.artifact)){
+ return false;
+ }
+ if (!taskGroupId.equals(that.taskGroupId)){
+ return false;
+ }
+ if (!taskVersion.equals(that.taskVersion)){
+ return false;
+ }
+ if (!taskExtension.equals(that.taskExtension)){
+ return false;
+ }
+ if (taskClassifier != null ? !taskClassifier.equals(that.taskClassifier) : that.taskClassifier != null){
+ return false;
+ }
+ return properties != null ? properties.equals(that.properties) : that.properties == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = artifact != null ? artifact.hashCode() : 0;
+ result = 31 * result + (taskGroupId != null ? taskGroupId.hashCode() : 0);
+ result = 31 * result + (taskVersion != null ? taskVersion.hashCode() : 0);
+ result = 31 * result + (taskExtension != null ? taskExtension.hashCode() : 0);
+ result = 31 * result + (taskClassifier != null ? taskClassifier.hashCode() : 0);
+ result = 31 * result + (properties != null ? properties.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/TaskLauncherConfiguration.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/TaskLauncherConfiguration.java
new file mode 100644
index 00000000..df96aa33
--- /dev/null
+++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/TaskLauncherConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.cloud.task.launcher;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cloud.deployer.spi.local.LocalDeployerProperties;
+import org.springframework.cloud.deployer.spi.local.LocalTaskLauncher;
+import org.springframework.cloud.deployer.spi.task.TaskLauncher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Creates the appropriate Task Launcher Configuration based on the TaskLauncher
+ * that is available in the classpath.
+ * @author Glenn Renfro
+ */
+
+@Configuration
+@ConditionalOnClass({TaskLauncher.class})
+public class TaskLauncherConfiguration {
+
+ @Configuration
+ @ConditionalOnMissingBean(name = "taskLauncher")
+ @ConditionalOnClass({LocalTaskLauncher.class})
+ protected static class LocalTaskDeployerConfiguration {
+ @Bean
+ public TaskLauncher taskLauncher() {
+ return new LocalTaskLauncher(new LocalDeployerProperties());
+ }
+ }
+
+}
diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/TaskLauncherSink.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/TaskLauncherSink.java
new file mode 100644
index 00000000..2c0f1f74
--- /dev/null
+++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/TaskLauncherSink.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.cloud.task.launcher;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.deployer.resource.maven.MavenResource;
+import org.springframework.cloud.deployer.spi.core.AppDefinition;
+import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
+import org.springframework.cloud.deployer.spi.task.TaskLauncher;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.messaging.Sink;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.util.Assert;
+
+/**
+ * A sink stream application that launches a tasks.
+ *
+ * @author Glenn Renfro
+ */
+
+@EnableBinding(Sink.class)
+public class TaskLauncherSink {
+
+ private final static Logger logger = LoggerFactory.getLogger(TaskLauncherSink.class);
+
+ @Autowired
+ public TaskLauncher taskLauncher;
+
+ /**
+ * Launches a task upon the receipt of a valid TaskLaunchRequest.
+ * @param request is a TaskLaunchRequest containing the information required to launch
+ * a task.
+ */
+ @ServiceActivator(inputChannel = Sink.INPUT)
+ public void taskLauncherSink(TaskLaunchRequest request) {
+ launchTask(request);
+ }
+
+ private void launchTask(TaskLaunchRequest taskLaunchRequest) {
+ Assert.notNull(taskLauncher, "TaskLauncher has not been initialized");
+ logger.info("Launching Task for the following resource " + taskLaunchRequest);
+ MavenResource resource = new MavenResource.Builder()
+ .artifactId(taskLaunchRequest.getArtifact())
+ .groupId(taskLaunchRequest.getTaskGroupId())
+ .version(taskLaunchRequest.getTaskVersion())
+ .extension(taskLaunchRequest.getTaskExtension())
+ .classifier(taskLaunchRequest.getTaskClassifier())
+ .build();
+ AppDefinition definition = new AppDefinition(taskLaunchRequest.getArtifact(), taskLaunchRequest.getProperties());
+ AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);
+ taskLauncher.launch(request);
+ }
+
+}
diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/annotation/EnableTaskLauncher.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/annotation/EnableTaskLauncher.java
new file mode 100644
index 00000000..441fb635
--- /dev/null
+++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/launcher/annotation/EnableTaskLauncher.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.cloud.task.launcher.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.cloud.deployer.spi.task.TaskLauncher;
+import org.springframework.cloud.task.launcher.TaskLaunchRequest;
+import org.springframework.cloud.task.launcher.TaskLauncherConfiguration;
+import org.springframework.cloud.task.launcher.TaskLauncherSink;
+import org.springframework.context.annotation.Import;
+
+/**
+ *
+ * Enable this boot app to be a sink to receive a {@link TaskLaunchRequest} and use the
+ * {@link TaskLauncher} to launch the task.
+ *
+ *
+ *
+ * @Configuration
+ * @EnableTaskLauncher
+ * public class AppConfig {
+ *
+ * @Bean
+ * public MyCommandLineRunner myCommandLineRunner() {
+ * return new MyCommandLineRunner()
+ * }
+ * }
+ *
+ *
+ * Note that only one of your configuration classes needs to have the @EnableTaskLauncher
+ * annotation.
+ *
+ * @author Glenn Renfro
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import({TaskLauncherConfiguration.class, TaskLauncherSink.class})
+public @interface EnableTaskLauncher {
+}
diff --git a/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/TaskLauncherSinkTests.java b/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/TaskLauncherSinkTests.java
new file mode 100644
index 00000000..357111b2
--- /dev/null
+++ b/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/TaskLauncherSinkTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.cloud.task.launcher;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.cloud.deployer.spi.task.LaunchState;
+import org.springframework.cloud.stream.annotation.Bindings;
+import org.springframework.cloud.stream.messaging.Sink;
+import org.springframework.cloud.task.launcher.configuration.TaskConfiguration;
+import org.springframework.cloud.task.launcher.util.TaskLauncherSinkApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = {TaskLauncherSinkApplication.class, TaskConfiguration.class} )
+public class TaskLauncherSinkTests {
+
+ private final static String DEFAULT_STATUS = "test_status";
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Autowired
+ @Bindings(TaskLauncherSink.class)
+ private Sink sink;
+
+
+ @Test
+ public void testSuccess() {
+ TaskConfiguration.TestTaskLauncher testTaskLauncher =
+ context.getBean(TaskConfiguration.TestTaskLauncher.class);
+
+ Map properties = new HashMap<>();
+ properties.put("server.port", "0");
+ TaskLaunchRequest request = new TaskLaunchRequest("timestamp-task",
+ "org.springframework.cloud.task.module","1.0.0.BUILD-SNAPSHOT", "jar",
+ "exec", properties);
+ GenericMessage message = new GenericMessage<>(request);
+ this.sink.input().send(message);
+ assertEquals(LaunchState.complete, testTaskLauncher.status(DEFAULT_STATUS).getState());
+ }
+
+ @Test
+ public void testNoRun() {
+ TaskConfiguration.TestTaskLauncher testTaskLauncher =
+ context.getBean(TaskConfiguration.TestTaskLauncher.class);
+
+ assertEquals(LaunchState.unknown, testTaskLauncher.status(DEFAULT_STATUS).getState());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoTaskLauncher() {
+ Map properties = new HashMap<>();
+ properties.put("server.port", "0");
+ TaskLauncherSink sink = new TaskLauncherSink();
+ sink.taskLauncherSink(new TaskLaunchRequest("timestamp-task",
+ "org.springframework.cloud.task.module","1.0.0.BUILD-SNAPSHOT", "jar",
+ "exec", properties));
+ }
+}
diff --git a/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/configuration/TaskConfiguration.java b/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/configuration/TaskConfiguration.java
new file mode 100644
index 00000000..00848056
--- /dev/null
+++ b/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/configuration/TaskConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.cloud.task.launcher.configuration;
+
+import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
+import org.springframework.cloud.deployer.spi.task.LaunchState;
+import org.springframework.cloud.deployer.spi.task.TaskLauncher;
+import org.springframework.cloud.deployer.spi.task.TaskStatus;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Glenn Renfro
+ */
+
+@Configuration
+public class TaskConfiguration {
+
+ @Bean
+ public TaskLauncher taskLauncher(){
+ return new TestTaskLauncher();
+ }
+
+ public static class TestTaskLauncher implements TaskLauncher{
+
+ public static final String LAUNCH_ID = "TEST_LAUNCH_ID";
+
+ private LaunchState state = LaunchState.unknown;
+
+ @Override
+ public String launch(AppDeploymentRequest request) {
+ state = LaunchState.complete;
+ return null;
+ }
+
+ @Override
+ public void cancel(String id) {
+
+ }
+
+ @Override
+ public TaskStatus status(String id) {
+ return new TaskStatus(LAUNCH_ID, state, null);
+ }
+ }
+}
diff --git a/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/util/TaskLauncherSinkApplication.java b/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/util/TaskLauncherSinkApplication.java
new file mode 100644
index 00000000..141d7eb3
--- /dev/null
+++ b/spring-cloud-task-stream/src/test/java/org/springframework/cloud/task/launcher/util/TaskLauncherSinkApplication.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed 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.springframework.cloud.task.launcher.util;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.task.launcher.annotation.EnableTaskLauncher;
+
+/**
+ * @author Glenn Renfro
+ */
+@SpringBootApplication
+@EnableTaskLauncher
+public class TaskLauncherSinkApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TaskLauncherSinkApplication.class, args);
+ }
+}