diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/Banner.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/Banner.java index 19318d4e5c1..1624982daeb 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/Banner.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/Banner.java @@ -41,7 +41,9 @@ abstract class Banner { for (String line : BANNER) { printStream.println(line); } - printStream.println(); + String version = Banner.class.getPackage().getImplementationVersion(); + printStream.println(" Spring Bootstrap" + + (version == null ? "" : " (v" + version + ")")); printStream.println(); } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java index fb931c14028..737473036a1 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java @@ -23,6 +23,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; @@ -129,10 +131,16 @@ public class SpringApplication { private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; + private final Log log = LogFactory.getLog(getClass()); + private Object[] sources; + private Class mainApplicationClass; + private boolean showBanner = true; + private boolean logStartupInfo = true; + private boolean addCommandLineProperties = true; private ResourceLoader resourceLoader; @@ -193,6 +201,7 @@ public class SpringApplication { for (ApplicationContextInitializer initializer : factories) { this.initializers.add(initializer); } + this.mainApplicationClass = deduceMainApplicationClass(); } private boolean deduceWebEnvironment() { @@ -204,6 +213,21 @@ public class SpringApplication { return true; } + private Class deduceMainApplicationClass() { + try { + StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); + for (StackTraceElement stackTraceElement : stackTrace) { + if ("main".equals(stackTraceElement.getMethodName())) { + return Class.forName(stackTraceElement.getClassName()); + } + } + } + catch (ClassNotFoundException ex) { + // Swallow and continue + } + return null; + } + /** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. @@ -220,6 +244,9 @@ public class SpringApplication { if (context instanceof ConfigurableApplicationContext) { applyInitializers((ConfigurableApplicationContext) context); } + if (this.logStartupInfo) { + logStartupInfo(); + } load(context, this.sources); refresh(context); runCommandLineRunners(context, args); @@ -251,6 +278,21 @@ public class SpringApplication { } } + protected void logStartupInfo() { + new StartupInfoLogger(this.mainApplicationClass).log(getApplicationLog()); + } + + /** + * Returns the {@link Log} for the application. By default will be deduced. + * @return the application log + */ + protected Log getApplicationLog() { + if (this.mainApplicationClass == null) { + return this.log; + } + return LogFactory.getLog(this.mainApplicationClass); + } + /** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context @@ -454,6 +496,16 @@ public class SpringApplication { ((AbstractApplicationContext) applicationContext).refresh(); } + /** + * Set a specific main application class that will be used as a log source and to + * obtain version information. By default the main application class will be deduced. + * Can be set to {@code null} if there is no explicit application class. + * @param mainApplicationClass the mainApplicationClass to set or {@code null} + */ + public void setMainApplicationClass(Class mainApplicationClass) { + this.mainApplicationClass = mainApplicationClass; + } + /** * Sets if this application is running within a web environment. If not specified will * attempt to deduce the environment based on the classpath. @@ -473,6 +525,15 @@ public class SpringApplication { this.showBanner = showBanner; } + /** + * Sets if the application information should be logged when the application starts. + * Defaults to {@code true} + * @param logStartupInfo if startup info should be logged. + */ + public void setLogStartupInfo(boolean logStartupInfo) { + this.logStartupInfo = logStartupInfo; + } + /** * Sets if a {@link CommandLinePropertySource} should be added to the application * context in order to expose arguments. Defaults to {@code true}. @@ -590,6 +651,21 @@ public class SpringApplication { return new SpringApplication(sources).run(args); } + /** + * Static helper that can be used to run a {@link SpringApplication} from a script + * using the specified sources with default settings. This method is useful when + * calling this calls from a script environment that will not have a single main + * application class. + * @param sources the sources to load + * @param args the application arguments (usually passed from a Java main method) + * @return the running {@link ApplicationContext} + */ + public static ApplicationContext runFromScript(Object[] sources, String[] args) { + SpringApplication application = new SpringApplication(sources); + application.setMainApplicationClass(null); + return application.run(args); + } + /** * Static helper that can be used to exit a {@link SpringApplication} and obtain a * code indicating success (0) or otherwise. Does not throw exceptions but should diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/StartupInfoLogger.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/StartupInfoLogger.java new file mode 100644 index 00000000000..368b9d48e41 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/StartupInfoLogger.java @@ -0,0 +1,143 @@ +/* + * 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.bootstrap; + +import java.io.File; +import java.net.InetAddress; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.ProtectionDomain; +import java.util.concurrent.Callable; + +import org.apache.commons.logging.Log; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Logs application information on startup. + * + * @author Phillip Webb + */ +class StartupInfoLogger { + + private final Class sourceClass; + + public StartupInfoLogger(Class sourceClass) { + this.sourceClass = sourceClass; + } + + public void log(Log log) { + Assert.notNull(log, "Log must not be null"); + StringBuilder message = new StringBuilder(); + message.append("Starting "); + message.append(getApplicationName()); + message.append(getVersion()); + message.append(getOn()); + message.append(getPid()); + message.append(getContext()); + log.info(message); + } + + private String getApplicationName() { + return (this.sourceClass != null ? ClassUtils.getShortName(this.sourceClass) + : "application"); + } + + private String getVersion() { + return getValue(" v", new Callable() { + @Override + public Object call() throws Exception { + return StartupInfoLogger.this.sourceClass.getPackage() + .getImplementationVersion(); + } + }); + } + + private String getOn() { + return getValue(" on ", new Callable() { + @Override + public Object call() throws Exception { + return InetAddress.getLocalHost().getHostName(); + } + }); + } + + private String getPid() { + return getValue(" with PID ", new Callable() { + @Override + public Object call() throws Exception { + return System.getProperty("PID"); + } + }); + } + + private String getContext() { + String startedBy = getValue("started by ", new Callable() { + @Override + public Object call() throws Exception { + return System.getProperty("user.name"); + } + }); + File codeSourceLocation = getCodeSourceLocation(); + String path = (codeSourceLocation == null ? "" : codeSourceLocation + .getAbsolutePath()); + if (startedBy == null && codeSourceLocation == null) { + return ""; + } + if (StringUtils.hasLength(startedBy) && StringUtils.hasLength(path)) { + startedBy = " " + startedBy; + } + return " (" + path + startedBy + ")"; + } + + private File getCodeSourceLocation() { + try { + ProtectionDomain protectionDomain = (this.sourceClass == null ? getClass() + : this.sourceClass).getProtectionDomain(); + URL location = protectionDomain.getCodeSource().getLocation(); + File file; + URLConnection connection = location.openConnection(); + if (connection instanceof JarURLConnection) { + file = new File(((JarURLConnection) connection).getJarFile().getName()); + } + else { + file = new File(location.getPath()); + } + if (file.exists()) { + return file; + } + } + catch (Exception ex) { + } + return null; + } + + private String getValue(String prefix, Callable call) { + try { + Object value = call.call(); + if (value != null && StringUtils.hasLength(value.toString())) { + return prefix + value; + } + } + catch (Exception ex) { + // Swallow and continue + } + return ""; + } +} diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/BannerTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/BannerTests.java new file mode 100644 index 00000000000..98b63c4be65 --- /dev/null +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/BannerTests.java @@ -0,0 +1,33 @@ +/* + * 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.bootstrap; + +import org.junit.Test; + +/** + * Tests for {@link Banner}. + * + * @author Phillip Webb + */ +public class BannerTests { + + @Test + public void visualBannder() throws Exception { + Banner.write(System.out); + } + +} diff --git a/spring-cli/src/main/java/org/springframework/cli/runner/SpringApplicationRunner.java b/spring-cli/src/main/java/org/springframework/cli/runner/SpringApplicationRunner.java index ade7d11b96f..30956f23c14 100644 --- a/spring-cli/src/main/java/org/springframework/cli/runner/SpringApplicationRunner.java +++ b/spring-cli/src/main/java/org/springframework/cli/runner/SpringApplicationRunner.java @@ -131,7 +131,7 @@ public class SpringApplicationRunner { // User reflection to load and call Spring Class application = getContextClassLoader().loadClass( "org.springframework.bootstrap.SpringApplication"); - Method method = application.getMethod("run", Object[].class, + Method method = application.getMethod("runFromScript", Object[].class, String[].class); this.applicationContext = method.invoke(null, this.sources, SpringApplicationRunner.this.args);