diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index f314e94c961..cc0db6f7208 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -78,7 +78,7 @@ spring-snapshots Spring Snapshots - http://maven.springframework.org/snapshot + http://repo.spring.io/snapshot true @@ -86,7 +86,7 @@ spring-milestones Spring Milestones - http://maven.springframework.org/milestone + http://repo.spring.io/milestone false @@ -96,7 +96,7 @@ spring-snapshots Spring Snapshots - http://maven.springframework.org/snapshot + http://repo.spring.io/snapshot true @@ -104,7 +104,15 @@ spring-milestones Spring Milestones - http://maven.springframework.org/milestone + http://repo.spring.io/milestone + + false + + + + spring-releases + Spring Releases + http://repo.spring.io/release false diff --git a/spring-boot-samples/spring-boot-sample-simple/src/test/resources/application.properties b/spring-boot-samples/spring-boot-sample-simple/src/test/resources/application.properties new file mode 100644 index 00000000000..4dfe84cedc6 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-simple/src/test/resources/application.properties @@ -0,0 +1 @@ +name: Phil \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-web-ui/build.gradle b/spring-boot-samples/spring-boot-sample-web-ui/build.gradle new file mode 100644 index 00000000000..6c4af83251f --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-ui/build.gradle @@ -0,0 +1,42 @@ +buildscript { + ext { + springBootVersion = '1.0.0.BUILD-SNAPSHOT' + springLoadedVersion = '1.1.5.RELEASE' + } + repositories { + mavenLocal() + maven { url "http://repo.springsource.org/libs-snapshot" } + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath("org.springsource.loaded:springloaded:${springLoadedVersion}") + } +} + + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'spring-boot' + +mainClassName = "sample.ui.SampleWebUiApplication" + +jar { + baseName = 'spring-boot-sample-simple' + version = '0.5.0' +} + +repositories { + mavenCentral() + maven { url "http://repo.springsource.org/libs-snapshot" } +} + +dependencies { + compile("org.springframework.boot:spring-boot-starter-thymeleaf") + compile("org.hibernate:hibernate-validator") + testCompile("org.springframework.boot:spring-boot-starter-test") +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.6' +} diff --git a/spring-boot-samples/spring-boot-sample-web-ui/pom.xml b/spring-boot-samples/spring-boot-sample-web-ui/pom.xml index fa28a87580d..d4e84bfd21b 100644 --- a/spring-boot-samples/spring-boot-sample-web-ui/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-ui/pom.xml @@ -32,6 +32,13 @@ org.springframework.boot spring-boot-maven-plugin + + + org.springsource.loaded + springloaded + 1.1.5.RELEASE + + diff --git a/spring-boot-samples/spring-boot-sample-web-ui/src/main/java/sample/ui/mvc/MessageController.java b/spring-boot-samples/spring-boot-sample-web-ui/src/main/java/sample/ui/mvc/MessageController.java index aacd5c78e06..5fe1f3992f1 100644 --- a/spring-boot-samples/spring-boot-sample-web-ui/src/main/java/sample/ui/mvc/MessageController.java +++ b/spring-boot-samples/spring-boot-sample-web-ui/src/main/java/sample/ui/mvc/MessageController.java @@ -68,7 +68,7 @@ public class MessageController { return new ModelAndView("redirect:/{message.id}", "message.id", message.getId()); } - @RequestMapping("/foo") + @RequestMapping("foo") public String foo() { throw new RuntimeException("Expected exception in controller"); } diff --git a/spring-boot-starters/spring-boot-starter-parent/pom.xml b/spring-boot-starters/spring-boot-starter-parent/pom.xml index f6e66d59816..8f70dc4d3d2 100644 --- a/spring-boot-starters/spring-boot-starter-parent/pom.xml +++ b/spring-boot-starters/spring-boot-starter-parent/pom.xml @@ -327,7 +327,7 @@ spring-milestones Spring Milestones - http://repo.springsource.org/milestone + http://repo.spring.io/milestone false diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.java index d3006d013c5..a73fd71e440 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.java @@ -24,8 +24,11 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.JavaExec; +import org.springframework.boot.gradle.task.ComputeMain; import org.springframework.boot.gradle.task.Repackage; import org.springframework.boot.gradle.task.RunApp; +import org.springframework.boot.gradle.task.RunWithAgent; /** * Gradle 'Spring Boot' {@link Plugin}. @@ -41,28 +44,41 @@ public class SpringBootPlugin implements Plugin { @Override public void apply(Project project) { - project.getPlugins().apply(BasePlugin.class); - project.getPlugins().apply(JavaPlugin.class); - project.getPlugins().apply(ApplicationPlugin.class); - project.getExtensions().create("springBoot", SpringBootPluginExtension.class); applyRepackage(project); applyRun(project); + + project.getPlugins().apply(BasePlugin.class); + project.getPlugins().apply(JavaPlugin.class); + project.getExtensions().create("springBoot", SpringBootPluginExtension.class); + applyResolutionStrategy(project); + } private void applyRepackage(Project project) { Repackage packageTask = addRepackageTask(project); ensureTaskRunsOnAssembly(project, packageTask); - } - - private void applyRun(Project project) { - addRunAppTask(project); // register BootRepackage so that we can use task foo(type: BootRepackage) {} project.getExtensions().getExtraProperties() .set("BootRepackage", Repackage.class); } + private void applyRun(Project project) { + enhanceRunTask(project); + addRunAppTask(project); + if (project.getTasks().withType(JavaExec.class).isEmpty()) { + // Add the ApplicationPlugin so that a JavaExec task is available (run) to enhance + project.getPlugins().apply(ApplicationPlugin.class); + } + } + + private void enhanceRunTask(Project project) { + project.getLogger().debug("Enhancing run tasks"); + project.getTasks().whenTaskAdded(new RunWithAgent(project)); + project.getTasks().whenTaskAdded(new ComputeMain(project)); + } + private void applyResolutionStrategy(Project project) { project.getConfigurations().all(new Action() { @@ -92,7 +108,13 @@ public class SpringBootPlugin implements Plugin { runJarTask.setDescription("Run the project with support for " + "auto-detecting main class and reloading static resources"); runJarTask.setGroup("Execution"); - runJarTask.dependsOn("assemble"); + if (!project.getTasksByName("compileJava", false).isEmpty()) { + if (!project.getTasksByName("compileGroovy", false).isEmpty()) { + runJarTask.dependsOn("compileJava", "compileGroovy"); + } else { + runJarTask.dependsOn("compileJava"); + } + } } private void ensureTaskRunsOnAssembly(Project project, Repackage task) { diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy index d69c9b5dad6..45c08d7de0e 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy @@ -98,4 +98,15 @@ public class SpringBootPluginExtension { Layout convertLayout() { (layout == null ? null : layout.layout) } + + /** + * Location of an agent jar to attach to the VM when running the application with runJar task. + */ + File agent; + + /** + * Flag to indicate that the agent requires -noverify (and the plugin will refuse to start if it is not set) + */ + Boolean noverify; + } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java new file mode 100644 index 00000000000..e796b6a318b --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2013 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.boot.gradle.task; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.SourceSet; +import org.springframework.boot.loader.tools.MainClassFinder; + +/** + * Add a main class if one is missing from the build + * + * @author Dave Syer + */ +public class ComputeMain implements Action { + + private Project project; + + public ComputeMain(Project project) { + this.project = project; + } + + @Override + public void execute(Task task) { + if (task instanceof JavaExec) { + final JavaExec exec = (JavaExec) task; + project.afterEvaluate(new Action() { + @Override + public void execute(Project project) { + addMain(exec); + } + }); + } + } + + private void addMain(JavaExec exec) { + if (exec.getMain()==null) { + project.getLogger().debug("Computing main for: " + exec); + project.setProperty("mainClassName", findMainClass(project)); + } + } + + private String findMainClass(Project project) { + SourceSet main = findMainSourceSet(project); + if (main == null) { + return null; + } + project.getLogger().debug("Looking for main in: " + main.getOutput().getClassesDir()); + try { + String mainClass = MainClassFinder.findMainClass(main.getOutput().getClassesDir()); + project.getLogger().info("Computed main class: " + mainClass); + return mainClass; + } + catch (IOException ex) { + throw new IllegalStateException("Cannot find main class", ex); + } + } + + public static SourceSet findMainSourceSet(Project project) { + final AtomicReference main = new AtomicReference(); + JavaPluginConvention javaConvention = project.getConvention().getPlugin( + JavaPluginConvention.class); + javaConvention.getSourceSets().all(new Action() { + + @Override + public void execute(SourceSet set) { + if (SourceSet.MAIN_SOURCE_SET_NAME.equals(set.getName())) { + main.set(set); + } + }; + + }); + return main.get(); + } +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java index 3782e0c72f5..48b52223432 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java @@ -28,7 +28,6 @@ import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.internal.file.collections.SimpleFileCollection; -import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; @@ -41,29 +40,20 @@ import org.springframework.boot.loader.tools.MainClassFinder; */ public class RunApp extends DefaultTask { - private SourceSet main; - @TaskAction public void runApp() { - + final Project project = getProject(); - JavaPluginConvention javaConvention = project.getConvention().getPlugin( - JavaPluginConvention.class); - javaConvention.getSourceSets().all(new Action() { - - @Override - public void execute(SourceSet set) { - if (SourceSet.MAIN_SOURCE_SET_NAME.equals(set.getName())) { - RunApp.this.main = set; - } - }; - - }); + final SourceSet main = ComputeMain.findMainSourceSet(project); final Set allResources = new LinkedHashSet(); - if (this.main != null) { - SourceDirectorySet resources = this.main.getResources(); + final File outputs; + if (main != null) { + SourceDirectorySet resources = main.getResources(); allResources.addAll(resources.getSrcDirs()); + outputs = main.getOutput().getResourcesDir(); + } else { + outputs = null; } project.getTasks().withType(JavaExec.class, new Action() { @@ -76,7 +66,7 @@ public class RunApp extends DefaultTask { getLogger().info("Adding classpath: " + allResources); exec.setClasspath(new SimpleFileCollection(files)); if (exec.getMain() == null) { - final String mainClass = findMainClass(RunApp.this.main); + final String mainClass = findMainClass(main); exec.setMain(mainClass); exec.getConventionMapping().map("main", new Callable() { @@ -88,6 +78,14 @@ public class RunApp extends DefaultTask { }); getLogger().info("Found main: " + mainClass); } + if (outputs != null) { + // Special case: this file causes logback to worry that it has been + // configured twice, so remove it from the target directory... + File logback = new File(outputs, "logback.xml"); + if (logback.exists()) { + logback.delete(); + } + } exec.exec(); } }); diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunWithAgent.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunWithAgent.java new file mode 100644 index 00000000000..5388656f090 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunWithAgent.java @@ -0,0 +1,139 @@ +/* + * Copyright 2012-2013 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.boot.gradle.task; + +import java.io.File; +import java.security.CodeSource; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.tasks.JavaExec; +import org.springframework.boot.gradle.SpringBootPluginExtension; +import org.springframework.boot.loader.tools.AgentAttacher; +import org.springframework.core.task.TaskRejectedException; + +/** + * Add a java agent to the "run" task if configured. You can add an agent in 3 ways (4 if + * you want to use native gradle features as well): + * + *
    + *
  1. Use "-Prun.agent=[path-to-jar]" on the gradle command line
  2. + *
  3. Add an "agent" property (jar file) to the "springBoot" extension in build.gradle
  4. + *
  5. As a special case springloaded is detected as a build script dependency
  6. + *
+ * + * @author Dave Syer + */ +public class RunWithAgent implements Action { + + private File agent; + + private Project project; + + private Boolean noverify; + + public RunWithAgent(Project project) { + this.project = project; + } + + @Override + public void execute(Task task) { + if (task instanceof JavaExec) { + final JavaExec exec = (JavaExec) task; + project.afterEvaluate(new Action() { + @Override + public void execute(Project project) { + addAgent(exec); + } + }); + } + if (task instanceof RunApp) { + final RunApp exec = (RunApp) task; + project.beforeEvaluate(new Action() { + @Override + public void execute(Project project) { + addAgent(exec); + } + }); + } + } + + private void addAgent(RunApp exec) { + project.getLogger().debug("Attaching to: " + exec); + findAgent(project.getExtensions().getByType(SpringBootPluginExtension.class)); + if (this.agent != null) { + exec.doFirst(new Action() { + @Override + public void execute(Task task) { + project.getLogger().info( + "Attaching agent: " + RunWithAgent.this.agent); + if (RunWithAgent.this.noverify!=null && RunWithAgent.this.noverify && !AgentAttacher.hasNoVerify()) { + throw new TaskRejectedException( + "The JVM must be started with -noverify for this agent to work. You can use JAVA_OPTS to add that flag."); + } + AgentAttacher.attach(RunWithAgent.this.agent); + } + }); + } + } + + private void addAgent(JavaExec exec) { + project.getLogger().debug("Attaching to: " + exec); + findAgent(project.getExtensions().getByType(SpringBootPluginExtension.class)); + if (this.agent != null) { + project.getLogger().info("Attaching agent: " + this.agent); + exec.jvmArgs("-javaagent:" + this.agent.getAbsolutePath()); + if (this.noverify != null && this.noverify) { + exec.jvmArgs("-noverify"); + } + } + } + + private void findAgent(SpringBootPluginExtension extension) { + if (this.agent != null) { + return; + } + this.noverify = project.getExtensions() + .getByType(SpringBootPluginExtension.class).getNoverify(); + project.getLogger().info("Finding agent"); + if (project.hasProperty("run.agent")) { + this.agent = project.file(project.property("run.agent")); + } else if (extension.getAgent() != null) { + this.agent = extension.getAgent(); + } + if (this.agent == null) { + try { + Class loaded = Class + .forName("org.springsource.loaded.agent.SpringLoadedAgent"); + if (this.agent == null && loaded != null) { + if (this.noverify==null) { + this.noverify = true; + } + CodeSource source = loaded.getProtectionDomain().getCodeSource(); + if (source != null) { + this.agent = new File(source.getLocation().getFile()); + } + } + } catch (ClassNotFoundException e) { + // ignore; + } + } + project.getLogger().debug("Agent: " + this.agent); + } + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/pom.xml b/spring-boot-tools/spring-boot-loader-tools/pom.xml index 2045dfbc2d2..0ab99b5b6a3 100644 --- a/spring-boot-tools/spring-boot-loader-tools/pom.xml +++ b/spring-boot-tools/spring-boot-loader-tools/pom.xml @@ -13,6 +13,13 @@ + + com.sun + tools + ${java.version} + system + ${java.home}/../lib/tools.jar + org.springframework spring-core diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AgentAttacher.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AgentAttacher.java new file mode 100644 index 00000000000..c6f15b5c008 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AgentAttacher.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2013 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.boot.loader.tools; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.util.List; + +import com.sun.tools.attach.VirtualMachine; + +/** + * @author Dave Syer + */ +public abstract class AgentAttacher { + + public static void attach(File agent) { + String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); + int p = nameOfRunningVM.indexOf('@'); + String pid = nameOfRunningVM.substring(0, p); + + try { + VirtualMachine vm = VirtualMachine.attach(pid); + vm.loadAgent(agent.getAbsolutePath()); + vm.detach(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static List commandLineArguments() { + return ManagementFactory.getRuntimeMXBean().getInputArguments(); + } + + public static boolean hasNoVerify() { + return commandLineArguments().contains("-Xverify:none"); + } + +} diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java index 00375d8f70d..2b6af77b0f4 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.security.CodeSource; import java.util.ArrayList; import java.util.List; @@ -36,6 +37,7 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.springframework.boot.loader.tools.AgentAttacher; import org.springframework.boot.loader.tools.MainClassFinder; /** @@ -63,6 +65,18 @@ public class RunMojo extends AbstractMojo { @Parameter(property = "run.addResources", defaultValue = "true") private boolean addResources; + /** + * Path to agent jar. + */ + @Parameter(property = "run.agent") + private File agent; + + /** + * Flag to say that the agent requires -noverify. + */ + @Parameter(property = "run.noverify") + private Boolean noverify; + /** * Arguments that should be passed to the application. */ @@ -91,7 +105,39 @@ public class RunMojo extends AbstractMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { + findAgent(); + if (this.agent != null) { + getLog().info("Attaching agent: " + this.agent); + if (this.noverify != null && this.noverify && !AgentAttacher.hasNoVerify()) { + throw new MojoExecutionException( + "The JVM must be started with -noverify for this agent to work. You can use MAVEN_OPTS to add that flag."); + } + AgentAttacher.attach(this.agent); + } final String startClassName = getStartClass(); + run(startClassName); + } + + private void findAgent() { + try { + Class loaded = Class + .forName("org.springsource.loaded.agent.SpringLoadedAgent"); + if (this.agent == null && loaded != null) { + if (this.noverify == null) { + this.noverify = true; + } + CodeSource source = loaded.getProtectionDomain().getCodeSource(); + if (source != null) { + this.agent = new File(source.getLocation().getFile()); + } + } + } + catch (ClassNotFoundException e) { + // ignore; + } + } + + private void run(String startClassName) throws MojoExecutionException { IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName); Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName, this.arguments), startClassName + ".main()");