Skip to content
Browse files

moved code from Grails core

  • Loading branch information...
1 parent d9c2d0d commit c230f3829f7d269b84d6875e4a58c2e5366933f8 @burtbeckwith burtbeckwith committed
View
15 .classpath
@@ -1,21 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
+ <classpathentry kind="src" path="src/groovy"/>
+ <classpathentry kind="src" path="src/java"/>
<classpathentry kind="src" path="grails-app/conf"/>
+ <classpathentry kind="src" path="scripts"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry exported="true" kind="con" path="GROOVY_DSL_SUPPORT"/>
- <classpathentry kind="src" path=".link_to_grails_plugins/release-2.0.4/src/groovy">
+ <classpathentry kind="src" path=".link_to_grails_plugins/release-2.2.1/src/groovy">
<attributes>
- <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
+ <attribute name="org.grails.ide.eclipse.core.SOURCE_FOLDER" value="true"/>
</attributes>
</classpathentry>
- <classpathentry kind="src" path=".link_to_grails_plugins/release-2.0.4/src/java">
+ <classpathentry kind="src" path=".link_to_grails_plugins/release-2.2.1/src/java">
<attributes>
- <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
+ <attribute name="org.grails.ide.eclipse.core.SOURCE_FOLDER" value="true"/>
</attributes>
</classpathentry>
- <classpathentry kind="src" path=".link_to_grails_plugins/rest-client-builder-1.0.2/src/groovy">
+ <classpathentry kind="src" path=".link_to_grails_plugins/rest-client-builder-1.0.3/src/groovy">
<attributes>
- <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
+ <attribute name="org.grails.ide.eclipse.core.SOURCE_FOLDER" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.grails.ide.eclipse.core.CLASSPATH_CONTAINER"/>
View
5 TomcatGrailsPlugin.groovy
@@ -1,4 +1,4 @@
-/* Copyright 2004-2012 SpringSource.
+/* Copyright 2004-2013 SpringSource.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* limitations under the License.
*/
class TomcatGrailsPlugin {
- def version = "2.3.0.BUILD-SNAPSHOT"
+ def version = "7.0.37.BUILD-SNAPSHOT"
def grailsVersion = "2.3 > *"
def scopes = [excludes: 'war']
def author = "Graeme Rocher"
@@ -21,7 +21,6 @@ class TomcatGrailsPlugin {
def title = "Apache Tomcat plugin for Grails"
def description = 'Makes Tomcat 7.0 the default servlet container for Grails at development time.'
def documentation = "http://grails.org/plugin/tomcat"
-
def license = 'APACHE'
def organization = [name: 'SpringSource', url: 'http://www.springsource.org/']
def issueManagement = [system: 'JIRA', url: 'http://jira.grails.org/browse/GPTOMCAT']
View
2 application.properties
@@ -1,3 +1 @@
app.grails.version=2.3.0.BUILD-SNAPSHOT
-app.name=tomcat
-
View
0 grails-app/.gitignore
No changes.
View
19 grails-app/conf/BuildConfig.groovy
@@ -1,10 +1,11 @@
-tomcatVersion = "7.0.37"
-
grails.project.work.dir = 'target'
grails.project.dependency.resolution = {
- inherits "global"
+ inherits "global", {
+ // TODO remove
+ excludes 'grails-plugin-tomcat', 'grails-tomcat'
+ }
log "warn"
repositories {
@@ -12,6 +13,9 @@ grails.project.dependency.resolution = {
}
dependencies {
+
+ String tomcatVersion = "7.0.37"
+
runtime("org.apache.tomcat:tomcat-catalina-ant:$tomcatVersion") {
excludes 'tomcat-catalina', 'tomcat-coyote'
}
@@ -20,19 +24,18 @@ grails.project.dependency.resolution = {
runtime "org.apache.tomcat.embed:tomcat-embed-logging-log4j:$tomcatVersion"
runtime "org.apache.tomcat.embed:tomcat-embed-logging-juli:$tomcatVersion"
- // needed for JSP compilation
- runtime "org.eclipse.jdt.core.compiler:ecj:3.7.2"
-
+ // needed for JSP compilation
+ runtime "org.eclipse.jdt.core.compiler:ecj:3.7.2"
+
compile("org.grails:grails-plugin-tomcat:$grailsVersion") {
excludes group: "org.grails", name: "grails-core"
excludes group: "org.grails", name: "grails-bootstrap"
excludes group: "org.grails", name: "grails-web"
-
}
}
plugins {
- build(':release:2.2.0', ':rest-client-builder:1.0.2') {
+ build(':release:2.2.1', ':rest-client-builder:1.0.3') {
export = false
}
}
View
187 src/groovy/org/grails/plugins/tomcat/InlineExplodedTomcatServer.groovy
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2011 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.grails.plugins.tomcat
+
+import grails.util.Holders
+
+import static grails.build.logging.GrailsConsole.instance as CONSOLE
+import grails.util.Environment
+import grails.util.GrailsNameUtils
+
+import org.apache.catalina.Context
+import org.apache.catalina.Loader
+import org.apache.catalina.connector.Connector
+import org.apache.catalina.startup.Tomcat
+import org.apache.coyote.http11.Http11NioProtocol
+import org.apache.tomcat.util.scan.StandardJarScanner
+import org.codehaus.groovy.grails.plugins.GrailsPluginUtils
+import org.grails.plugins.tomcat.fork.ForkedTomcatServer
+
+/**
+ * Serves the app, without packaging as a war and runs it in the same JVM.
+ */
+class InlineExplodedTomcatServer extends TomcatServer {
+
+ final Tomcat tomcat = new Tomcat()
+
+ Context context
+
+ InlineExplodedTomcatServer(String basedir, String webXml, String contextPath, ClassLoader classLoader) {
+
+ if (contextPath == '/') {
+ contextPath = ''
+ }
+
+ tomcat.setBaseDir(tomcatDir.absolutePath)
+ context = tomcat.addWebapp(contextPath, basedir)
+ boolean shouldScan = checkAndInitializingClasspathScanning()
+
+ def jarScanner = new StandardJarScanner()
+ jarScanner.setScanClassPath(shouldScan)
+ context.setJarScanner(jarScanner)
+
+ tomcat.enableNaming()
+
+ // we handle reloading manually
+ context.reloadable = false
+ context.setAltDDName(getWorkDirFile("resources/web.xml").absolutePath)
+
+ configureAliases(context)
+
+ def loader = createTomcatLoader(classLoader)
+ loader.container = context
+ context.loader = loader
+ initialize(tomcat)
+ }
+
+ protected void initialize(Tomcat tomcat) {
+ // do nothing, for subclasses to override
+ }
+
+ protected void configureAliases(Context context) {
+ def aliases = []
+ def pluginManager = Holders.getPluginManager()
+
+ if (pluginManager != null) {
+ for (plugin in pluginManager.userPlugins) {
+ def dir = pluginSettings.getPluginDirForName(GrailsNameUtils.getScriptName(plugin.name))
+ def webappDir = dir ? new File("${dir.file.absolutePath}/web-app") : null
+ if (webappDir?.exists()) {
+ aliases << "/plugins/${plugin.fileSystemName}=${webappDir.absolutePath}"
+ }
+ }
+ }
+
+ if (aliases) {
+ context.setAliases(aliases.join(','))
+ }
+ }
+
+ protected Loader createTomcatLoader(ClassLoader classLoader) {
+ new TomcatLoader(classLoader)
+ }
+
+ void doStart(String host, int httpPort, int httpsPort) {
+ preStart()
+
+ if (host != "localhost") {
+ tomcat.connector.setAttribute("address", host)
+ tomcat.connector.setAttribute("port", httpPort)
+ }
+
+ if (getConfigParam("nio")) {
+ CONSOLE.updateStatus "Enabling Tomcat NIO connector"
+ def connector = new Connector(Http11NioProtocol.name)
+ connector.port = httpPort
+ tomcat.service.addConnector(connector)
+ tomcat.connector = connector
+ }
+
+ tomcat.port = httpPort
+ tomcat.connector.URIEncoding = 'UTF-8'
+
+ if (httpsPort) {
+ def sslConnector = loadInstance('org.apache.catalina.connector.Connector')
+ sslConnector.scheme = "https"
+ sslConnector.secure = true
+ sslConnector.port = httpsPort
+ sslConnector.setProperty("SSLEnabled", "true")
+ sslConnector.setAttribute("keystoreFile", keystoreFile.absolutePath)
+ sslConnector.setAttribute("keystorePass", keyPassword)
+ sslConnector.URIEncoding = 'UTF-8'
+
+ if (host != "localhost") {
+ sslConnector.setAttribute("address", host)
+ }
+
+ if (truststoreFile.exists()) {
+ CONSOLE.addStatus "Using truststore $truststore"
+ sslConnector.setAttribute("truststoreFile", truststore)
+ sslConnector.setAttribute("truststorePass", trustPassword)
+ sslConnector.setAttribute("clientAuth", getConfigParam("clientAuth") ?: "want")
+ }
+
+ tomcat.service.addConnector(sslConnector)
+ }
+
+ if (Environment.isFork()) {
+ ForkedTomcatServer.startKillSwitch(tomcat, httpPort)
+ }
+ tomcat.start()
+
+ }
+
+ void stop() {
+ tomcat.stop()
+ tomcat.destroy()
+ GrailsPluginUtils.clearCaches()
+ }
+
+ private loadInstance(String name) {
+ tomcat.class.classLoader.loadClass(name).newInstance()
+ }
+
+ private preStart() {
+ eventListener?.triggerEvent("ConfigureTomcat", tomcat)
+ def jndiEntries = grailsConfig?.grails?.naming?.entries
+
+ if (!(jndiEntries instanceof Map)) {
+ return
+ }
+
+ System.setProperty("javax.sql.DataSource.Factory", "org.apache.commons.dbcp.BasicDataSourceFactory")
+
+ jndiEntries.each { name, resCfg ->
+ if (resCfg) {
+ if (!resCfg["type"]) {
+ throw new IllegalArgumentException("Must supply a resource type for JNDI configuration")
+ }
+ def res = loadInstance('org.apache.catalina.deploy.ContextResource')
+ res.name = name
+ res.type = resCfg.remove("type")
+ res.auth = resCfg.remove("auth")
+ res.description = resCfg.remove("description")
+ res.scope = resCfg.remove("scope")
+ // now it's only the custom properties left in the Map...
+ resCfg.each { key, value ->
+ res.setProperty(key, value)
+ }
+
+ context.namingResources.addResource res
+ }
+ }
+ }
+}
View
154 src/groovy/org/grails/plugins/tomcat/IsolatedWarTomcatServer.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2011 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.grails.plugins.tomcat
+
+import grails.build.logging.GrailsConsole
+import grails.util.BuildSettings
+//import groovy.transform.CompileStatic
+
+import org.codehaus.groovy.grails.cli.logging.GrailsConsoleAntBuilder
+
+/**
+ * Serves a packaged war, in a forked JVM.
+ *
+ * @author Graeme Rocher
+ * @deprecated No longer used, Replaced by {@link org.grails.plugins.tomcat.fork.TomcatWarRunner} and {@link org.grails.plugins.tomcat.fork.ForkedTomcatServer}
+ */
+class IsolatedWarTomcatServer extends TomcatServer {
+
+ static DEFAULT_JVM_ARGS = ["-Xmx512m"]
+ static DEFAULT_STARTUP_TIMEOUT_SECS = 300 // 5 mins
+
+ protected final File warDir
+ protected final String contextPath
+ protected ant = new GrailsConsoleAntBuilder()
+
+ IsolatedWarTomcatServer(String warPath, String contextPath) {
+ super()
+
+ warDir = getWorkDirFile("war")
+ ant.delete(dir: warDir, failonerror: false)
+ ant.unzip(src: warPath, dest: warDir)
+
+ this.contextPath = contextPath == '/' ? '' : contextPath
+ }
+
+ void doStart(String host, int httpPort, int httpsPort) {
+ def outFile = new File(buildSettings.projectTargetDir, "tomcat-out.txt")
+ def errFile = new File(buildSettings.projectTargetDir, "tomcat-err.txt")
+ [outFile, errFile].each { ant.delete(file: it, failonerror: false) }
+
+ def resultProperty = "tomcat.result"
+
+ System.setProperty("TomcatKillSwitch.active", "true")
+ Thread.start("tomcat process runner") {
+ ant.java(classname: IsolatedTomcat.name, fork: true, failonerror: false, output: outFile, error: errFile, resultproperty: resultProperty) {
+
+ classpath {
+ for (jar in findTomcatJars(buildSettings)) {
+ pathelement location: jar
+ }
+ }
+
+ arg value: tomcatDir
+ arg value: warDir.absolutePath
+ arg value: contextPath
+ arg value: host
+ arg value: httpPort
+
+ if (httpsPort) {
+ arg value: httpsPort
+ arg value: keystoreFile.absolutePath
+ arg value: keyPassword
+ }
+
+ for (a in (getConfigParam('jvmArgs') ?: DEFAULT_JVM_ARGS)) {
+ jvmarg value: a
+ }
+
+ for (entry in getConfigParams()) {
+ sysproperty key:"tomcat.${entry.key}", value:"${entry.value}"
+ }
+ }
+ }
+
+ Runtime.addShutdownHook { this.stop() }
+ Thread.start {
+ // start a thread to monitor kill if server was killed
+ sleep(10000)
+ while(true) {
+ try {
+ new Socket(host, httpPort)
+ sleep(5000)
+ } catch (e) {
+ println "bad"
+ System.setProperty("TomcatKillSwitch.active", "false")
+ break
+ }
+ }
+ }
+
+ def timeoutSecs = getConfigParam('startupTimeoutSecs') ?: DEFAULT_STARTUP_TIMEOUT_SECS
+ def interval = 500 // half a second
+
+ def loops = Math.ceil((timeoutSecs * 1000) / interval)
+ def started = false
+ def i = 0
+
+ while (!started && i++ < loops) {
+ // make sure tomcat didn't error starting up
+ def resultCode = ant.project.properties."$resultProperty"
+ if (resultCode != null) {
+ def err = ""
+ try { err = errFile.text } catch (IOException e) {}
+ throw new RuntimeException("tomcat exited prematurely with code '$resultCode' (error output: '$err')")
+ }
+
+ // look for the magic string that will be written to output when the app is running
+ try {
+ started = outFile.text.contains("Server running. ")
+ } catch (IOException e) {
+ started = false
+ }
+
+ if (!started) { // wait a bit then try again
+ Thread.sleep(interval as long)
+ }
+ }
+
+ if (!started) { // we didn't start in the specified timeout
+ throw new RuntimeException("Tomcat failed to start the app in $timeoutSecs seconds (see output in $outFile.path)")
+ }
+
+ GrailsConsole.instance.log "Tomcat Server running WAR (output written to: $outFile)"
+ }
+
+// @CompileStatic
+ static Collection<File> findTomcatJars(BuildSettings buildSettings) {
+ return buildSettings.buildDependencies.findAll { File it -> it.name.contains("tomcat") } +
+ buildSettings.compileDependencies.findAll { File it -> it.name.contains("tomcat") } +
+ buildSettings.runtimeDependencies.findAll { File it -> it.name.contains("tomcat") } +
+ buildSettings.providedDependencies.findAll { File it -> it.name.contains("tomcat") }
+ }
+
+ void stop() {
+ try {
+ new URL("http://${warParams.host}:${warParams.port + 1}").text
+ } catch(e) {
+ // ignore
+ }
+ }
+}
View
102 src/groovy/org/grails/plugins/tomcat/TomcatLoader.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2011 SpringSource
+ *
+ * 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.grails.plugins.tomcat
+
+import java.beans.PropertyChangeListener
+
+import org.apache.catalina.Container
+import org.apache.catalina.Lifecycle
+import org.apache.catalina.LifecycleState
+import org.apache.catalina.Loader
+import org.apache.catalina.util.LifecycleBase
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.apache.naming.resources.DirContextURLStreamHandler
+import org.apache.naming.resources.DirContextURLStreamHandlerFactory
+
+/**
+ * A loader instance used for the embedded version of Tomcat 7.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+class TomcatLoader extends LifecycleBase implements Loader {
+
+ private static Log log = LogFactory.getLog(TomcatLoader.name)
+
+ private static boolean first = true
+
+ ClassLoader classLoader
+ Container container
+ boolean delegate
+ boolean reloadable
+
+ TomcatLoader(ClassLoader classLoader) {
+ // Class loader that only searches the parent
+ this.classLoader = new ParentDelegatingClassLoader(classLoader)
+ }
+
+ void addPropertyChangeListener(PropertyChangeListener listener) {}
+
+ void addRepository(String repository) {
+ log.warn "Call to addRepository($repository) was ignored."
+ }
+
+ void backgroundProcess() {}
+
+ String[] findRepositories() {
+ log.warn "Call to findRepositories() returned null."
+ }
+
+ String getInfo() { "MyLoader/1.0" }
+
+ boolean modified() { false }
+
+ void removePropertyChangeListener(PropertyChangeListener listener) {}
+
+ @Override protected void initInternal() {
+ URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory()
+
+ if (first) {
+ first = false
+ try {
+ URL.setURLStreamHandlerFactory(streamHandlerFactory)
+ } catch (Exception e) {
+ // Log and continue anyway, this is not critical
+ log.error("Error registering jndi stream handler", e)
+ } catch (Throwable t) {
+ // This is likely a dual registration
+ log.info("Dual registration of jndi stream handler: " + t.getMessage())
+ }
+ }
+
+ DirContextURLStreamHandler.bind(classLoader, container.getResources())
+ }
+
+ @Override protected void destroyInternal() {
+ classLoader = null
+ }
+
+ @Override protected void startInternal() {
+ fireLifecycleEvent(Lifecycle.START_EVENT, this)
+ setState(LifecycleState.STARTING)
+ }
+
+ @Override protected void stopInternal() {
+ fireLifecycleEvent(Lifecycle.STOP_EVENT, this)
+ setState(LifecycleState.STOPPING)
+ }
+}
View
197 src/groovy/org/grails/plugins/tomcat/TomcatServer.groovy
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2011 SpringSource
+ *
+ * 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.grails.plugins.tomcat
+
+import static grails.build.logging.GrailsConsole.instance as CONSOLE
+import grails.util.BuildSettings
+import grails.util.BuildSettingsHolder
+import grails.util.PluginBuildSettings
+import grails.web.container.EmbeddableServer
+
+import org.apache.tomcat.util.scan.StandardJarScanner
+import org.codehaus.groovy.grails.cli.support.GrailsBuildEventListener
+import org.codehaus.groovy.grails.plugins.GrailsPluginUtils
+import org.springframework.util.ReflectionUtils
+
+/**
+ * Provides common functionality for the inline and isolated variants of tomcat server.
+ *
+ * @author Graeme Rocher
+ * @see org.grails.plugins.tomcat.fork.TomcatWarRunner
+ * @see org.grails.plugins.tomcat.fork.TomcatDevelopmentRunner
+ */
+abstract class TomcatServer implements EmbeddableServer {
+
+ protected final BuildSettings buildSettings
+ protected final PluginBuildSettings pluginSettings
+
+ protected final File workDir
+ protected final File tomcatDir
+
+ protected final boolean usingUserKeystore
+ protected final File keystoreFile
+ protected final String keyPassword
+ protected String truststore
+ protected File truststoreFile
+ protected String trustPassword
+
+ // These are set from the outside in _GrailsRun
+ def grailsConfig
+ GrailsBuildEventListener eventListener
+
+ TomcatServer() {
+ buildSettings = BuildSettingsHolder.getSettings()
+ pluginSettings = GrailsPluginUtils.getPluginBuildSettings()
+
+ workDir = buildSettings.projectWorkDir
+ tomcatDir = getWorkDirFile("tomcat")
+
+ def userKeystore = getConfigParam("keystorePath")
+ if (userKeystore) {
+ usingUserKeystore = true
+ keystoreFile = new File(userKeystore)
+ keyPassword = getConfigParam("keystorePassword") ?: "changeit" // changeit is the keystore default
+ } else {
+ usingUserKeystore = false
+ keystoreFile = getWorkDirFile("ssl/keystore")
+ keyPassword = "123456"
+ }
+
+ def userTruststore = getConfigParam("truststorePath")
+ if (userKeystore) {
+ truststore = userTruststore
+ trustPassword = getConfigParam("truststorePassword") ?: "changeit"
+ } else {
+ truststore = "${buildSettings.grailsWorkDir}/ssl/truststore"
+ trustPassword = "123456"
+ }
+
+ truststoreFile = new File(truststore)
+
+ System.setProperty('org.mortbay.xml.XmlParser.NotValidating', 'true')
+
+ tomcatDir.deleteDir()
+ }
+
+ protected boolean checkAndInitializingClasspathScanning() {
+ def scanConfig = getConfigParam("scan")
+ def shouldScan = (Boolean) (scanConfig.enabled instanceof Boolean ? scanConfig.enabled : false)
+ def extraJarsToSkip = scanConfig.excludes
+ if (extraJarsToSkip instanceof List && shouldScan) {
+ try {
+ def jarsToSkipField = ReflectionUtils.findField(StandardJarScanner, "defaultJarsToSkip", Set)
+ ReflectionUtils.makeAccessible(jarsToSkipField)
+ Set jarsToSkip = jarsToSkipField.get(StandardJarScanner)
+ jarsToSkip.addAll(extraJarsToSkip)
+ } catch (e) {
+ // ignore
+ }
+ }
+ shouldScan
+ }
+
+ /**
+ * The host and port params will never be null, defaults will be passed if necessary.
+ *
+ * If httpsPort is > 0, the server should listen for https requests on that port.
+ */
+ protected abstract void doStart(String host, int httpPort, int httpsPort)
+
+ /**
+ * Shutdown the server.
+ */
+ abstract void stop()
+
+ void restart() {
+ stop()
+ start()
+ }
+
+ void start() {
+ start(null, null)
+ }
+
+ void start(int port) {
+ start(null, port)
+ }
+
+ void start(String host, int port) {
+ doStart(host ?: DEFAULT_HOST, port ?: DEFAULT_PORT, 0)
+ }
+
+ void startSecure() {
+ startSecure(null)
+ }
+
+ void startSecure(int port) {
+ startSecure(null, null, port)
+ }
+
+ void startSecure(String host, int httpPort, int httpsPort) {
+ if (!keystoreFile.exists()) {
+ if (usingUserKeystore) {
+ throw new IllegalStateException("cannot start tomcat in https because use keystore does not exist (value: $keystoreFile)")
+ } else {
+ createSSLCertificate()
+ }
+ }
+
+ doStart(host ?: DEFAULT_HOST, httpPort ?: DEFAULT_PORT, httpsPort ?: DEFAULT_SECURE_PORT)
+ }
+
+ protected File getWorkDirFile(String path) {
+ new File(workDir, path)
+ }
+
+ protected getConfigParam(String name) {
+ buildSettings.config.grails.tomcat[name]
+ }
+
+ protected Map getConfigParams() {
+ buildSettings.config.grails.tomcat
+ }
+
+ protected createSSLCertificate() {
+ CONSOLE.updateStatus 'Creating SSL Certificate...'
+
+ def keystoreDir = keystoreFile.parentFile
+ if (!keystoreDir.exists() && !keystoreDir.mkdir()) {
+ throw new RuntimeException("Unable to create keystore folder: " + keystoreDir.canonicalPath)
+ }
+
+ getKeyToolClass().main(
+ "-genkey",
+ "-alias", "localhost",
+ "-dname", "CN=localhost,OU=Test,O=Test,C=US",
+ "-keyalg", "RSA",
+ "-validity", "365",
+ "-storepass", "key",
+ "-keystore", keystoreFile.absolutePath,
+ "-storepass", keyPassword,
+ "-keypass", keyPassword)
+
+ println 'Created SSL Certificate.'
+ }
+
+ private getKeyToolClass() {
+ try {
+ Class.forName('sun.security.tools.KeyTool')
+ } catch (ClassNotFoundException e) {
+ // no try/catch for this one, if neither is found let it fail
+ Class.forName('com.ibm.crypto.tools.KeyTool')
+ }
+ }
+}
View
74 src/groovy/org/grails/plugins/tomcat/TomcatServerFactory.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 SpringSource
+ *
+ * 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.grails.plugins.tomcat
+
+import grails.util.BuildSettings
+import grails.web.container.EmbeddableServer
+import grails.web.container.EmbeddableServerFactory
+import groovy.transform.CompileStatic
+
+import org.codehaus.groovy.grails.cli.support.BuildSettingsAware
+import org.grails.plugins.tomcat.fork.ForkedTomcatServer
+import org.grails.plugins.tomcat.fork.TomcatExecutionContext
+
+class TomcatServerFactory implements EmbeddableServerFactory, BuildSettingsAware {
+
+ BuildSettings buildSettings
+
+ @CompileStatic
+ EmbeddableServer createInline(String basedir, String webXml, String contextPath, ClassLoader classLoader) {
+ final obj = buildSettings?.forkSettings?.get("run")
+ if (obj) {
+ return createForked(contextPath, obj)
+ }
+
+ return new InlineExplodedTomcatServer(basedir, webXml, contextPath, classLoader)
+ }
+
+ @CompileStatic
+ private ForkedTomcatServer createForked(String contextPath, forkConfig, boolean warMode = false) {
+ final settings = buildSettings
+ TomcatExecutionContext ec = new TomcatExecutionContext()
+ ec.initialize(settings)
+ ec.contextPath = contextPath
+ ec.resourcesDir = settings.resourcesDir
+ if (warMode) {
+ ec.warPath = settings.projectWarFile.canonicalPath
+ }
+
+ final forkedTomcat = new ForkedTomcatServer(ec)
+ if (forkConfig instanceof Map) {
+ forkedTomcat.configure((Map)forkConfig)
+ }
+
+ def tomcatJvmArgs = getTomcatJvmArgs()
+ if (tomcatJvmArgs instanceof List) {
+ forkedTomcat.jvmArgs = (List<String>)tomcatJvmArgs
+ }
+
+ return forkedTomcat
+ }
+
+ private getTomcatJvmArgs() {
+ buildSettings.config?.grails?.tomcat?.jvmArgs
+ }
+
+ EmbeddableServer createForWAR(String warPath, String contextPath) {
+ buildSettings.projectWarFile = new File(warPath)
+ final forkConfig = buildSettings?.forkSettings?.get("war") ?: buildSettings?.forkSettings?.get("run") ?: [:]
+ return createForked(contextPath, forkConfig, true)
+ }
+}
View
159 src/groovy/org/grails/plugins/tomcat/fork/ForkedTomcatServer.groovy
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012 SpringSource
+ *
+ * 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.grails.plugins.tomcat.fork
+
+import grails.build.logging.GrailsConsole
+import grails.util.BuildSettings
+import grails.util.BuildSettingsHolder
+import grails.util.Environment
+import grails.web.container.EmbeddableServer
+import groovy.transform.CompileStatic
+import org.apache.catalina.startup.Tomcat
+import org.codehaus.groovy.grails.cli.fork.ExecutionContext
+import org.codehaus.groovy.grails.cli.fork.ForkedGrailsProcess
+import org.grails.plugins.tomcat.TomcatKillSwitch
+
+/**
+ * An implementation of the Tomcat server that runs in forked mode.
+ *
+ * @author Graeme Rocher
+ * @since 2.2
+ */
+class ForkedTomcatServer extends ForkedGrailsProcess implements EmbeddableServer {
+
+ public static final GrailsConsole CONSOLE = GrailsConsole.getInstance()
+ @Delegate EmbeddableServer tomcatRunner
+
+ ForkedTomcatServer(TomcatExecutionContext executionContext) {
+ this.executionContext = executionContext
+ this.forkReserve = true
+ }
+
+ private ForkedTomcatServer() {
+ executionContext = (TomcatExecutionContext)readExecutionContext()
+ if (executionContext == null) {
+ throw new IllegalStateException("Forked server created without first creating execution context and calling fork()")
+ }
+ }
+
+ static void main(String[] args) {
+ new ForkedTomcatServer().run()
+ }
+
+ @CompileStatic
+ def run() {
+ if (!isReserveProcess()) {
+ runInternal()
+ }
+ else {
+ CONSOLE.verbose("Waiting for resume signal for idle JVM")
+ waitForResume()
+ CONSOLE.verbose("Resuming idle JVM")
+ runInternal()
+ }
+ }
+
+ protected void runInternal() {
+ TomcatExecutionContext ec = (TomcatExecutionContext)executionContext
+ BuildSettings buildSettings = initializeBuildSettings(ec)
+ URLClassLoader classLoader = initializeClassLoader(buildSettings)
+ initializeLogging(ec.grailsHome, classLoader)
+
+ tomcatRunner = createTomcatRunner(buildSettings, ec, classLoader)
+ if (ec.securePort > 0) {
+ tomcatRunner.startSecure(ec.host, ec.port, ec.securePort)
+ } else {
+ tomcatRunner.start(ec.host, ec.port)
+ }
+
+ setupReloading(classLoader, buildSettings)
+ }
+
+ @Override
+ protected void discoverAndSetAgent(ExecutionContext executionContext) {
+ TomcatExecutionContext tec = (TomcatExecutionContext)executionContext
+ // no agent for war mode
+ if (!tec.warPath) {
+ super.discoverAndSetAgent(executionContext)
+ }
+ }
+
+ @CompileStatic
+ protected EmbeddableServer createTomcatRunner(BuildSettings buildSettings, TomcatExecutionContext ec, URLClassLoader classLoader) {
+ if (ec.warPath) {
+ if (Environment.isFork()) {
+ BuildSettings.initialiseDefaultLog4j(classLoader)
+ }
+
+ new TomcatWarRunner(ec.warPath, ec.contextPath)
+ }
+ else {
+ new TomcatDevelopmentRunner("$buildSettings.baseDir/web-app", buildSettings.webXmlLocation.absolutePath, ec.contextPath, classLoader)
+ }
+ }
+
+ @CompileStatic
+ void start(String host, int port) {
+ startSecure(host, port, 0)
+ }
+
+ @CompileStatic
+ void startSecure(String host, int httpPort, int httpsPort) {
+ final ec = (TomcatExecutionContext)executionContext
+ ec.host = host
+ ec.port = httpPort
+ ec.securePort = httpsPort
+ def t = new Thread( {
+ final process = fork()
+ Runtime.addShutdownHook {
+ try {
+ process.destroy()
+ } catch (e) {
+ // ignore, nothing we can do
+ }
+ }
+ } )
+
+ t.start()
+ while(!isAvailable(host, httpPort)) {
+ sleep 100
+ }
+ System.setProperty(TomcatKillSwitch.TOMCAT_KILL_SWITCH_ACTIVE, "true")
+ }
+
+ @CompileStatic
+ boolean isAvailable(String host, int port) {
+ try {
+ new Socket(host, port)
+ return true
+ } catch (e) {
+ return false
+ }
+ }
+
+ void stop() {
+ final ec = (TomcatExecutionContext)executionContext
+ try {
+ new URL("http://${ec?.host ?: 'localhost'}:${(ec?.port ?: 8080 ) + 1}").text
+ } catch(e) {
+ // ignore
+ }
+ }
+
+ static void startKillSwitch(final Tomcat tomcat, final int serverPort) {
+ new Thread(new TomcatKillSwitch(tomcat, serverPort)).start()
+ }
+}
View
88 src/groovy/org/grails/plugins/tomcat/fork/TomcatDevelopmentRunner.groovy
@@ -0,0 +1,88 @@
+package org.grails.plugins.tomcat.fork
+
+import groovy.transform.CompileStatic
+import org.apache.catalina.Context
+import org.apache.catalina.startup.Tomcat
+import org.codehaus.groovy.grails.io.support.Resource
+import org.codehaus.groovy.grails.plugins.GrailsPluginUtils
+import org.grails.plugins.tomcat.InlineExplodedTomcatServer
+
+/**
+ * @author Graeme Rocher
+ */
+class TomcatDevelopmentRunner extends InlineExplodedTomcatServer {
+
+ private String currentHost
+ private int currentPort
+ private ClassLoader forkedClassLoader
+
+ TomcatDevelopmentRunner(String basedir, String webXml, String contextPath, ClassLoader classLoader) {
+ super(basedir, webXml, contextPath, classLoader)
+ this.forkedClassLoader = classLoader
+ }
+
+ @Override
+ @CompileStatic
+ protected void initialize(Tomcat tomcat) {
+ final autodeployDir = buildSettings.autodeployDir
+ if (autodeployDir.exists()) {
+ final wars = autodeployDir.listFiles()
+ for (File f in wars) {
+ final fileName = f.name
+ if (fileName.endsWith(".war")) {
+ tomcat.addWebapp(f.name - '.war', f.absolutePath)
+ }
+ }
+ }
+
+ invokeCustomizer(tomcat)
+ }
+
+ private void invokeCustomizer(Tomcat tomcat) {
+ Class cls = null
+ try {
+ cls = forkedClassLoader.loadClass("org.grails.plugins.tomcat.ForkedTomcatCustomizer")
+ } catch (Throwable e) {
+ // ignore
+ }
+
+ if (cls != null) {
+ try {
+ cls.newInstance().customize(tomcat)
+ } catch (e) {
+ throw new RuntimeException("Error invoking Tomcat server customizer: " + e.getMessage(), e)
+ }
+ }
+ }
+
+ @Override
+ protected void configureAliases(Context context) {
+ def aliases = []
+ final directories = GrailsPluginUtils.getPluginDirectories()
+ for (Resource dir in directories) {
+ def webappDir = new File("${dir.file.absolutePath}/web-app")
+ if (webappDir.exists()) {
+ aliases << "/plugins/${dir.file.name}=${webappDir.absolutePath}"
+ }
+ }
+ if (aliases) {
+ context.setAliases(aliases.join(','))
+ }
+ }
+
+ @Override
+ void start(String host, int port) {
+ currentHost = host
+ currentPort = port
+ super.start(host, port)
+ }
+
+ @Override
+ void stop() {
+ try {
+ new URL("http://${currentHost}:${currentPort+ 1}").text
+ } catch(e) {
+ // ignore
+ }
+ }
+}
View
45 src/groovy/org/grails/plugins/tomcat/fork/TomcatExecutionContext.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 SpringSource
+ *
+ * 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.grails.plugins.tomcat.fork
+
+import grails.util.BuildSettings
+import grails.web.container.EmbeddableServer
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.grails.cli.fork.ExecutionContext
+
+/**
+ * Execution context for the forked Tomcat container
+ *
+ * @author Graeme Rocher
+ * @since 2.3
+ */
+@CompileStatic
+class TomcatExecutionContext extends ExecutionContext {
+ String contextPath
+ String host = EmbeddableServer.DEFAULT_HOST
+ int port = EmbeddableServer.DEFAULT_PORT
+ int securePort
+ String warPath
+
+ @Override
+ protected List<File> buildMinimalIsolatedClasspath(BuildSettings buildSettings) {
+ final buildDependencies = super.buildMinimalIsolatedClasspath(buildSettings)
+ final tomcatJars = org.codehaus.groovy.grails.cli.fork.ForkedGrailsProcess.findTomcatJars(buildSettings)
+ buildDependencies.addAll(tomcatJars.findAll { File f -> !f.name.contains('juli')})
+
+ return buildDependencies
+ }
+}
View
125 src/groovy/org/grails/plugins/tomcat/fork/TomcatWarRunner.groovy
@@ -0,0 +1,125 @@
+/*
+* Copyright 2013 SpringSource
+*
+* 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.grails.plugins.tomcat.fork
+
+import grails.build.logging.GrailsConsole
+import grails.util.Metadata
+import groovy.transform.CompileStatic
+
+import org.apache.catalina.LifecycleException
+import org.apache.catalina.connector.Connector
+import org.apache.catalina.startup.Tomcat
+import org.apache.coyote.http11.Http11NioProtocol
+import org.grails.plugins.tomcat.TomcatServer
+
+/**
+ * A Tomcat runner that runs a WAR file
+ *
+ * @author Graeme Rocher
+ * @since 2.3
+ */
+@CompileStatic
+class TomcatWarRunner extends TomcatServer {
+
+ private static final GrailsConsole CONSOLE = GrailsConsole.getInstance()
+
+ protected Tomcat tomcat = new Tomcat()
+ protected String warPath
+ protected String contextPath
+
+ TomcatWarRunner(String warPath, String contextPath) {
+ this.warPath = warPath
+ this.contextPath = contextPath
+ }
+
+ protected void enableSslConnector(String host, int httpsPort) {
+ Connector sslConnector
+ try {
+ sslConnector = new Connector()
+ } catch (Exception e) {
+ throw new RuntimeException("Couldn't create HTTPS connector", e)
+ }
+
+ sslConnector.setScheme("https")
+ sslConnector.setSecure(true)
+ sslConnector.setPort(httpsPort)
+ sslConnector.setProperty("SSLEnabled", "true")
+ sslConnector.setAttribute("keystoreFile", keystoreFile)
+ sslConnector.setAttribute("keystorePass", keyPassword)
+ sslConnector.setURIEncoding("UTF-8")
+
+ if (!host.equals("localhost")) {
+ sslConnector.setAttribute("address", host)
+ }
+
+ tomcat.getService().addConnector(sslConnector)
+ }
+
+ @Override
+ protected void doStart(String host, int httpPort, int httpsPort) {
+
+ Metadata.getCurrent().put(Metadata.WAR_DEPLOYED, "true")
+ tomcat.port = httpPort
+ tomcat.setSilent(true)
+
+ if (getConfigParam("nio")) {
+ CONSOLE.updateStatus("Enabling Tomcat NIO Connector")
+ def connector = new Connector(Http11NioProtocol.name)
+ connector.port = httpPort
+ tomcat.service.addConnector(connector)
+ tomcat.connector = connector
+ }
+
+ tomcat.baseDir = tomcatDir
+ try {
+ tomcat.addWebapp contextPath, warPath
+ } catch (Throwable e) {
+ CONSOLE.error("Error loading Tomcat: " + e.getMessage(), e)
+ System.exit(1)
+ }
+ tomcat.enableNaming()
+
+ final Connector connector = tomcat.getConnector()
+
+ // Only bind to host name if we aren't using the default
+ if (!host.equals("localhost")) {
+ connector.setAttribute("address", host)
+ }
+
+ connector.setURIEncoding("UTF-8")
+
+ if (httpsPort) {
+ enableSslConnector(host, httpsPort)
+ }
+
+ final int serverPort = httpPort
+ ForkedTomcatServer.startKillSwitch(tomcat, serverPort)
+
+ try {
+ tomcat.start()
+ String message = "Server running. Browse to http://"+(host != null ? host : "localhost")+":"+httpPort+contextPath
+ CONSOLE.addStatus(message)
+ } catch (LifecycleException e) {
+ CONSOLE.error("Error loading Tomcat: " + e.getMessage(), e)
+ System.exit(1)
+ }
+ }
+
+ @Override
+ void stop() {
+ tomcat.stop()
+ }
+}
View
140 src/java/org/grails/plugins/tomcat/IsolatedTomcat.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 SpringSource
+ *
+ * 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.grails.plugins.tomcat;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.coyote.http11.Http11NioProtocol;
+
+/**
+ * An isolated version of Tomcat used to run Grails applications with run-war.
+ *
+ * @author Graeme Rocher
+ * @deprecated No longer used, Replaced by {@link org.grails.plugins.tomcat.fork.TomcatWarRunner} and {@link org.grails.plugins.tomcat.fork.ForkedTomcatServer}
+ * @since 2.0
+ */
+public class IsolatedTomcat {
+
+ /**
+ * @param args Command line arguments
+ */
+ public static void main(String[] args) {
+
+ if (args.length < 3) {
+ System.err.println("Usage: IsolatedTomcat [tomcat_path] [war_path] [context_path] [host] [httpPort] [httpsPort] [keystorePath] [keystorePassword]");
+ System.exit(1);
+ }
+
+ String tomcatDir = args[0];
+ String warPath = args[1];
+ String contextPath = args[2];
+ String host = "localhost";
+ if (args.length>3) host = args[3];
+ int port = argToNumber(args, 4, 8080);
+ int httpsPort = argToNumber(args, 5, 0);
+
+ String keystorePath = "";
+ String keystorePassword = "";
+ if (httpsPort > 0) {
+ keystorePath = args[6];
+ keystorePassword = args[7];
+ }
+
+ final Tomcat tomcat = new Tomcat();
+ tomcat.setPort(port);
+
+ if (Boolean.getBoolean("tomcat.nio")) {
+ System.out.println("Enabling Tomcat NIO Connector");
+ Connector connector = new Connector(Http11NioProtocol.class.getName());
+ connector.setPort(port);
+ tomcat.getService().addConnector(connector);
+ tomcat.setConnector(connector);
+ }
+
+ tomcat.setBaseDir(tomcatDir);
+ try {
+ tomcat.addWebapp(contextPath, warPath);
+ } catch (ServletException e) {
+ e.printStackTrace();
+ System.err.println("Error loading Tomcat: " + e.getMessage());
+ System.exit(1);
+ }
+ tomcat.enableNaming();
+
+ final Connector connector = tomcat.getConnector();
+
+ // Only bind to host name if we aren't using the default
+ if (!host.equals("localhost")) {
+ connector.setAttribute("address", host);
+ }
+
+ connector.setURIEncoding("UTF-8");
+
+ if (httpsPort > 0) {
+ Connector sslConnector;
+ try {
+ sslConnector = new Connector();
+ } catch (Exception e) {
+ throw new RuntimeException("Couldn't create HTTPS connector", e);
+ }
+
+ sslConnector.setScheme("https");
+ sslConnector.setSecure(true);
+ sslConnector.setPort(httpsPort);
+ sslConnector.setProperty("SSLEnabled", "true");
+ sslConnector.setAttribute("keystoreFile", keystorePath);
+ sslConnector.setAttribute("keystorePass", keystorePassword);
+ sslConnector.setURIEncoding("UTF-8");
+
+ if (!host.equals("localhost")) {
+ sslConnector.setAttribute("address", host);
+ }
+
+ tomcat.getService().addConnector(sslConnector);
+ }
+
+ final int serverPort = port;
+ startKillSwitch(tomcat, serverPort);
+
+ try {
+ tomcat.start();
+ String message = "Server running. Browse to http://"+(host != null ? host : "localhost")+":"+port+contextPath;
+ System.out.println(message);
+ } catch (LifecycleException e) {
+ e.printStackTrace(System.err);
+ System.err.println("Error loading Tomcat: " + e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ public static void startKillSwitch(final Tomcat tomcat, final int serverPort) {
+ new Thread(new TomcatKillSwitch(tomcat, serverPort)).start();
+ }
+
+ private static int argToNumber(String[] args, int i, int orDefault) {
+ if (args.length > i) {
+ try {
+ return Integer.parseInt(args[i]);
+ } catch (NumberFormatException e) {
+ return orDefault;
+ }
+ }
+ return orDefault;
+ }
+}
View
62 src/java/org/grails/plugins/tomcat/ParentDelegatingClassLoader.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 SpringSource
+ *
+ * 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.grails.plugins.tomcat;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * A class loader that searches the parent
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class ParentDelegatingClassLoader extends ClassLoader{
+
+ private Method findClassMethod;
+
+ protected ParentDelegatingClassLoader(ClassLoader parent) {
+ super(parent);
+ findClassMethod = findMethod(ClassLoader.class,"findClass", String.class);
+ findClassMethod.setAccessible(true);
+ }
+
+ private Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
+ Class<?> searchType = clazz;
+ while (searchType != null) {
+ Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
+ for (Method method : methods) {
+ if (name.equals(method.getName())
+ && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
+ return method;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+ @Override
+ protected Class<?> findClass(String className) throws ClassNotFoundException {
+ try {
+ return (Class<?>) findClassMethod.invoke(getParent(), className);
+ } catch (IllegalAccessException e) {
+ throw new ClassNotFoundException(className);
+ } catch (InvocationTargetException e) {
+ throw new ClassNotFoundException(className);
+ }
+ }
+}
View
60 src/java/org/grails/plugins/tomcat/TomcatKillSwitch.java
@@ -0,0 +1,60 @@
+package org.grails.plugins.tomcat;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.startup.Tomcat;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Allows the tomcat server to be killed by pinging a port one greater than the server port.
+ *
+ * @author Graeme Rocher
+ * @since 2.1
+ */
+public class TomcatKillSwitch implements Runnable {
+
+ public static final String TOMCAT_KILL_SWITCH_ACTIVE = "TomcatKillSwitch.active";
+
+ private Tomcat tomcat;
+ private int serverPort;
+
+ public TomcatKillSwitch(Tomcat tomcat, int serverPort) {
+ this.tomcat = tomcat;
+ this.serverPort = serverPort;
+ }
+
+ public static boolean isActive() {
+ return Boolean.getBoolean("TomcatKillSwitch.active");
+ }
+
+ public void run() {
+ System.setProperty("TomcatKillSwitch.active", "true");
+ int killListenerPort = serverPort + 1;
+ ServerSocket serverSocket = createKillSwitch(killListenerPort);
+ if (serverSocket != null) {
+ try {
+ serverSocket.accept();
+ try {
+ tomcat.stop();
+ tomcat.destroy();
+ System.setProperty(TOMCAT_KILL_SWITCH_ACTIVE, "false");
+ System.exit(0);
+ } catch (LifecycleException e) {
+ System.err.println("Error stopping Tomcat: " + e.getMessage());
+ System.exit(1);
+ }
+ } catch (IOException e) {
+ // just exit
+ }
+ }
+ }
+
+ private static ServerSocket createKillSwitch(int killListenerPort) {
+ try {
+ return new ServerSocket(killListenerPort);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}

0 comments on commit c230f38

Please sign in to comment.
Something went wrong with that request. Please try again.