Fork me on GitHub

hipshoot Spring Boot Support

Introduction

This library provides the following components to support or extend Spring Boot Framework.

Component Description
Either AppsDeployingTomcatServletWebServerFactory in v2.x, or AppsDeployingTomcatEmbeddedServletContainerFactory in the earlier versions.

An extension of the Tomcat web server factory, to be able to deploy additional WARs packaged at classpath:META-INF/hipshoot/embedded-catalina/webapps/.

If an environment property, hipshoot.embedded.catalina.wars, is provided with a comma separated string for the war file names (e.g, hipshoot.embedded.catalina.wars="site.war, cms.war"), this will look up those war file resources under classpath:META-INF/hipshoot/embedded-catalina/webapps/, extract the war file resources to a local file system directory designated as local embedded tomcat webapps directory by another environment property, hipshoot.embedded.catalina.appBase, and add each war as web application context during the startup.

Please see CatalinaConfiguration for a full list of the available configuration properties.

DeferredInitDelegatingServlet

Delegating Servlet that defers the initialization of the delegate servlet and delegates calls to the delegate servlet specified in the init parameter, DeferredInitDelegatingServlet.delegateServletClass.

This delegating servlet with 'deferring initialization' feature can be useful if you want to start a web application fast enough without waiting for a servlet initialization to complete. For example, when you deploy spring boot based web application(s) onto a cloud based platform, you don't have to worry about the timeout issue (e.g, 60 seconds by default somewhere) due to a long time initializing servlet with this class. This servlet class will pass the servlet initialization phase fast because the real initialization of the delegate servlet will be deferred and done asynchronously so that the servlet container may start all the other web applications faster without having to wait for the delegate servlet to complete the initialization phase.

Please see DeferredInitDelegatingServlet for usages and details.

About AppsDeployingTomcatServletWebServerFactory in v2.x or AppsDeployingTomcatEmbeddedServletContainerFactory in the earlier versions

By default, the Tomcat web server container factory implementation does not provide some options to customize the embedded Tomcat server. For example, it doesn't allow to deploy additional web applications. It starts up only with single web applicaiton, by default. You are supposed to code somethings to customize the embedded Tomcat server manually in many cases. This might be okay in simple applications, but not convenient for multi-wars-based application such as Hippo CMS in which a content delivery application (e.g, site.war) and a content authoring application (e.g, cms.war) are deployed in most cases.

Either class extends the default Tomcat web server container factory class to customize the embedded Tomcat container.

How to use AppsDeployingTomcatServletWebServerFactory in v2.x or AppsDeployingTomcatEmbeddedServletContainerFactory in the earlier versions

Here's a simple Spring Boot Application class example with v2.x:

@Configuration
@SpringBootApplication
// To auto-scan org.onehippo.forge.hipshoot.spring.boot.support.config.embedded.CatalinaConfiguration bean
// that reads all the configuration properties (from application.yaml or application.properties for example).
@ComponentScan(basePackages = { "org.onehippo.forge.hipshoot.spring.boot.support.config.embedded" })
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    /**
     * Embedded tomcat configuration to pass to AppsDeployingTomcatEmbeddedServletContainerFactory
     */
    @Autowired
    private CatalinaConfiguration catalinaConfiguration;

    @Bean
    public ServletWebServerFactory servletContainer() {
        // Instead of TomcatServletWebServerFactory, return an AppsDeployingTomcatServletWebServerFactory here.
        return new AppsDeployingTomcatServletWebServerFactory(catalinaConfiguration);
    }
}
        

The following is a simple Spring Boot Application class example with the earlier versions:

@Configuration
@SpringBootApplication
// To auto-scan org.onehippo.forge.hipshoot.spring.boot.support.config.embedded.CatalinaConfiguration bean
// that reads all the configuration properties (from application.yaml or application.properties for example).
@ComponentScan(basePackages = { "org.onehippo.forge.hipshoot.spring.boot.support.config.embedded" })
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    /**
     * Embedded tomcat configuration to pass to AppsDeployingTomcatEmbeddedServletContainerFactory
     */
    @Autowired
    private CatalinaConfiguration catalinaConfiguration;

    @Bean
    public EmbeddedServletContainerFactory servletContainerFactory() {
        // Instead of TomcatEmbeddedServletContainerFactory, return an AppsDeployingTomcatEmbeddedServletContainerFactory here.
        return new AppsDeployingTomcatEmbeddedServletContainerFactory(catalinaConfiguration);
    }
}
        

Now, either of those will read all the configuration properties under hipshoot.embedded.catalina prefix (see the example configuration below), and do some extra work such as deploying additional wars as separate contexts, enabling JNDI naming, registering JNDI resources, etc.

# spring-boot-deploy/src/main/resources/application.yaml

logging:
    level:
        root: 'WARN'
        org.springframework.boot: 'INFO'
        org.onehippo.forge.hipshoot.spring.boot.support: 'INFO'
        org.hippoecm.hst.core.container.DiagnosticReportingValve: 'INFO'
        freemarker.servlet: 'FATAL'
        org.apache.wicket.Localizer: 'ERROR'
        org.hippoecm.repository.jackrabbit.persistence: 'INFO'
        org.hippoecm.repository.updater: 'INFO'
        org.apache.jackrabbit.core.persistence.bundle: 'INFO'
        org.apache.jackrabbit.core.state: 'ERROR'
        org.apache.jackrabbit.core.ItemManager: 'ERROR'
        org.apache.jackrabbit.core.persistence.bundle.util.LRUNodeIdCache: 'ERROR'
        com.github.woonsanko.examples.hippoboot.springboot: 'INFO'

server:
    tomcat:
        basedir: 'target/embedded-tomcat'

hipshoot:
    embedded:
        catalina:
            persistSession: 'false'
            appBase: '${server.tomcat.basedir}/webapps'
            wars: 'cms.war, site.war'
            server:
                defaultContext:
                    resources:
                        cachingAllowed: 'true'
                        cacheMaxSize: '40960'
                    environments:
                        - type: 'java.lang.String'
                          name: "foo"
                          value: "bar"
                    parameters:
                        - name: 'repository-address'
                          value: 'rmi://127.0.0.1:1099/hipporepository'
                          override: 'false'
                        - name: 'repository-directory'
                          value: 'storage'
                          override: 'false'
                    namingResources:
                        - name: 'mail/Session'
                          auth: 'Container'
                          type: 'javax.mail.Session'
                          propertiesString: 'mail.smtp.host=localhost, mail.smtp.port=2525'
                        - name: 'jdbc/repositoryDS'
                          auth: 'Container'
                          type: 'javax.sql.DataSource'
                          properties:
                              driverClassName: 'org.hsqldb.jdbc.JDBCDriver'
                              url: 'jdbc:hsqldb:mem:mymemdb'
                              username: 'SA'
                              password: ''
                              initialSize: '2'
                              maxTotal: '20'
                              maxIdle: '10'
                              minIdle: '2'
                              maxWaitMillis: '10000'
        

Deploying Additional WARs packages in the single executable JAR

AppsDeployingTomcatServletWebServerFactory in v2.x or AppsDeployingTomcatEmbeddedServletContainerFactory in the earlier versions reads hipshoot.embedded.catalina.wars configuration property. And, if the property has a comma separated war file resource names (e.g, "site.war, cms.war"), then it copies each war file resource from the single executable JAR file (under classpath:META-INF/hipshoot/embedded-catalina/webapps/) by using Thread.currentThread().getContextClassloader().getResourceAsStream(String) in order to deploy it additionally on startup. e.g, Thread.currentThread().getContextClassloader().getResourceAsStream("META-INF/hipshoot/embedded-catalina/webapps/cms.war").

Packaging Additional WARs packages in the single executable JAR

Let's suppose you have a normal Hippo project containing site and cms war modules. Each maven submodule will create a war file under the target folder.

To wrap this Hippo project into a Spring Boot application, you can do the following as an example:

  1. Add a submodule, spring-boot-deploy, under the root.
  2. So, this submodule will inherit Hippo CMS Release parent pom basically.
  3. Now, create a pom.xml in spring-boot-deploy module, setting parent to the root pom.
  4. Add Spring Boot Dependencies in spring-boot-deploy module as import scope. (ref: Using Spring Boot without the parent POM)
  5. Package both site.war and cms.war into the jar output before the Spring Boot Repackaging task. For example, we can execute maven-resources-plugin:copy-resources plugin in prepare-package Maven phase. (See the example below.)
  6. Configure the Spring Boot Repackage Maven plugin. (You might want to configure ZIP layout in order to take advantage of the features of PropertiesLauncher.)
  7. Finally, you can execute the packaged single executable JAR file simply with java command, or you can even use a specific platform tool (e.g, Cloud Foundry cf commands).

Here's an example pom.xml of spring-boot-deploy submodule explained above:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.github.woonsanko</groupId>
    <artifactId>hippo-on-spring-boot</artifactId>
    <version>2.1.0-SNAPSHOT</version>
  </parent>

  <artifactId>hippo-on-spring-boot-spring-boot-deploy</artifactId>
  <packaging>jar</packaging>
  <name>Example Hippo On Spring Boot Spring Boot Deploy</name>
  <description>Example Hippo On Spring Boot Spring Boot Deploy</description>

  <properties>
    <java.version>1.8</java.version>
    <spring-boot.version>2.1.0.RELEASE</spring-boot.version>
    <tomcat.version>9.0.12</tomcat.version>
  </properties>

  <dependencyManagement>

    <dependencies>

      <!-- Import dependency management from Spring Boot -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

    </dependencies>

  </dependencyManagement>

  <dependencies>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <scope>provided</scope>
    </dependency>

    <!-- Let this application be a spring boot web application simply. -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- To provide commons-logging API implementation as Spring depends on commons-logging API. -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <scope>provided</scope>
    </dependency>

    <!-- To enable Tomcat DBCP2 DataSource JNDI Resource. -->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-dbcp</artifactId>
      <version>${tomcat.version}</version>
      <scope>provided</scope>
    </dependency>

    <!-- JDBC Driver for JNDI DataSource -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>provided</scope>
    </dependency>

    <!-- To use Spring Boot Extension components provided by hipshoot-spring-boot-support. -->
    <dependency>
      <groupId>org.onehippo.forge.hipshoot</groupId>
      <artifactId>hipshoot-spring-boot-support</artifactId>
      <scope>provided</scope>
    </dependency>

    <!-- For JSP/JSTL Support in the embedded tomcat. -->
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-el</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
    </dependency>

  </dependencies>

  <build>

    <plugins>

      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-resources</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>copy-resources</goal>
            </goals>
            <configuration>
              <outputDirectory>${basedir}/target/classes/META-INF/hipshoot/embedded-catalina/webapps</outputDirectory>
              <resources>
                <resource>
                  <filtering>false</filtering>
                  <directory>${basedir}/../cms/target</directory>
                  <includes>
                    <include>*.war</include>
                  </includes>
                </resource>
                <resource>
                  <filtering>false</filtering>
                  <directory>${basedir}/../site/webapp/target</directory>
                  <includes>
                    <include>*.war</include>
                  </includes>
                </resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <layout>ZIP</layout>
          <executable>true</executable>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

    </plugins>

  </build>

</project>
        

Building and Runing

If you run mvn clean package in the project root folder, it will build and package all the WARs (e.g, site.war and cms.war). And, when it is building spring-boot-deploy submodule continously, it will add those war files into target/classes/META-INF/hipshoot/embedded-catalina/webapps/ folder first. After building a normal JAR package file, the Spring Boot Maven plugin will repackage it to be self-executable.

So, you can execute the JAR like the following example:

$ java \
    -Xms512m -Xmx1024m \
    -Drepo.path="storage" \
    -jar target/myhippoproject-spring-boot-deploy.jar