About the Author

Phillip Verheyden

Software Architect

@phillipuniverse
Published
in Development 13 min read

Deploying Broadleaf Commerce 2.0 on Heroku

This post is a continuation off of the 1.6 instructions on deploying Heroku on Broadleaf Commerce. Most of the instructions are pretty much the same. If you don't want to follow the instructions here, you can check out a ready-to-go Heroku configured application that I have on GitHub.

Let's start with getting the latest demo site Eclipse workspace (which is at M1-5 at the time of this writing). You can find this at https://github.com/BroadleafCommerce/DemoSite/downloads. This is already a little simpler than the 1.6 archetype; there are only 3 Maven modules here; core, admin and site.

Once that is downloaded, the first thing we need to do is change the database configuration. Let's first change the dialect to Postgres. In core/common-shared.properties:

--- a/core/src/main/resources/runtime-properties/common-shared.properties
+++ b/core/src/main/resources/runtime-properties/common-shared.properties
@@ -55,22 +55,22 @@ store.front.webapp.prefix=http://localhost:8080/
 
 # Settings for the default persistence unit
 blPU.hibernate.hbm2ddl.auto=validate
-blPU.hibernate.dialect=org.hibernate.dialect.HSQLDialect
+blPU.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
 blPU.hibernate.show_sql=false
 blPU.hibernate.cache.use_second_level_cache=true
 blPU.hibernate.cache.use_query_cache=true
 blPU.hibernate.hbm2ddl.import_files=null
 # Settings for the CMS storage persistence unit
 blCMSStorage.hibernate.hbm2ddl.auto=validate
-blCMSStorage.hibernate.dialect=org.hibernate.dialect.hSQLDialect
+blCMSStorage.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
 blCMSStorage.hibernate.show_sql=false
 blCMSStorage.hibernate.cache.use_second_level_cache=true
 blCMSStorage.hibernate.cache.use_query_cache=true
 blCMSStorage.hibernate.hbm2ddl.import_files=null
 # Settings for the secure persistence unit
 blSecurePU.hibernate.hbm2ddl.auto=validate
-blSecurePU.hibernate.dialect=org.hibernate.dialect.HSQLDialect
+blSecurePU.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
 blSecurePU.hibernate.show_sql=false
 blSecurePU.hibernate.cache.use_second_level_cache=false

Since Heroku gives environment properties for the database configuration, we cannot use the JNDI lookup and instead have to define our datasource within an aplication context. We'll do this in applicationContext-datasource.xml for both site and admin. You can just replace the applicationContext-datasource.xml with this:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="

        http://www.springframework.org/schema/jee

        http://www.springframework.org/schema/jee/spring-jee-3.1.xsd

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

        http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context-3.1.xsd

        http://www.springframework.org/schema/tx

        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
    
    <!-- The "webDS" data source is the main data source for Broadleaf. It is referenced and

         should be configured via JNDI in your particular environment. For local testing and

         development using Jetty, the JNDI data source is configured in the /WEB-INF/jetty-env.xml file.

         The other data sources are required as well.  They allow Broadleaf to use different databases

         for secure information such as payment info when in a PCI compliant situation, and/or for CMS

         if you wish to store content in a separate database. -->

    <bean class="java.net.URI" id="dbUrl">
        <constructor-arg value="${DATABASE_URL}"/>
    </bean>
    
    <bean id="webDS" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="#{ 'jdbc:postgresql://' + @dbUrl.getHost() + @dbUrl.getPath() }" />
        <property name="username" value="#{ @dbUrl.getUserInfo().split(':')[0] }" />
        <property name="password" value="#{ @dbUrl.getUserInfo().split(':')[1] }" />
    </bean>
    
    <bean id="webSecureDS" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="#{ 'jdbc:postgresql://' + @dbUrl.getHost() + @dbUrl.getPath() }" />
        <property name="username" value="#{ @dbUrl.getUserInfo().split(':')[0] }" />
        <property name="password" value="#{ @dbUrl.getUserInfo().split(':')[1] }" />
    </bean>
 
    <bean id="webStorageDS" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="#{ 'jdbc:postgresql://' + @dbUrl.getHost() + @dbUrl.getPath() }" />
        <property name="username" value="#{ @dbUrl.getUserInfo().split(':')[0] }" />
        <property name="password" value="#{ @dbUrl.getUserInfo().split(':')[1] }" />
    </bean>

</beans>

We now need to ensure the slug size is decreased. Heroku has raised their limit to 200MB, but with everything compiled and absent of a clean, we'll still end up going into ~220MB. The maven-clean-plugin should be included in all 3 projects, but tweaked slightly for each.

site/pom.xml (exclude the cargo directory from being deleted):

<plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.4.1</version>
    <configuration>
        <filesets>
            <fileset>
                <directory>target/</directory>
                <includes>
                    <include>**/*</include>
                </includes>
                <!-- This exclusion is only necessary in the site project is for the Tomcat plugin that will be addressed a little later -->
                <excludes>
                    <exclude>cargo/**</exclude>
                </excludes>
                <followSymlinks>false</followSymlinks>
            </fileset>
        </filesets>
        <excludeDefaultDirectories>true</excludeDefaultDirectories>
    </configuration>
    <executions>
        <execution>
            <id>auto-clean</id>
            <phase>install</phase>
            <goals>
                <goal>clean</goal>
            </goals>
        </execution>
    </executions>
</plugin>

admin/pom.xml (exclude admin.war from being deleted):

<plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.4.1</version>
    <configuration>
        <filesets>
            <fileset>
                <directory>target/</directory>
                <includes>
                    <include>**/*</include>
                </includes>
                <!-- don't delete the admin war; will be copied into the Tomcat deploy directory on startup -->
                <excludes>
                    <exclude>admin.war</exclude>
                </excludes>
                <followSymlinks>false</followSymlinks>
            </fileset>
        </filesets>
        <excludeDefaultDirectories>true</excludeDefaultDirectories>
    </configuration>
    <executions>
        <execution>
            <id>auto-clean</id>
            <phase>install</phase>
            <goals>
                <goal>clean</goal>
            </goals>
        </execution>
    </executions>
</plugin>

core/pom.xml (no need to exclude anything):

<plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.4.1</version>
    <configuration>
        <filesets>
            <fileset>
                <directory>target/</directory>
                <includes>
                    <include>**/*</include>
                </includes>
                <followSymlinks>false</followSymlinks>
            </fileset>
        </filesets>
        <excludeDefaultDirectories>true</excludeDefaultDirectories>
    </configuration>
    <executions>
        <execution>
            <id>auto-clean</id>
            <phase>install</phase>
            <goals>
                <goal>clean</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Now, completely remove the HSQL dependency in the parent pom, and replace it with Postgres:

<dependency>
    <groupId>postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>8.4-702.jdbc3</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

You will also need to reference this in both the admin and site poms:

<dependency>
    <groupId>postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

As I mentioned in the previous post, I had trouble getting embedded Jetty to work and so I went for a full-blown Tomcat 7 install. This also allows us to start up both the admin and site project in the same Tomcat instance. This is available through the cargo-maven2-plugin.

in site/pom.xml

<plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <configuration>
        <container>
            <containerId>tomcat6x</containerId>
            <zipUrlInstaller>
                <url>http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.18/bin/apache-tomcat-6.0.18.zip</url>
            </zipUrlInstaller>
            <dependencies>
                <dependency>
                    <groupId>javax.activation</groupId>
                    <artifactId>activation</artifactId>
                </dependency>
                <dependency>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </dependency>
            </dependencies>
        </container>
        <configuration>
            <type>standalone</type>
            <deployables>
                <deployable>
                    <groupId>com.phillip</groupId>
                    <artifactId>site-war</artifactId>
                    <type>war</type>
                    <properties>
                        <context>ROOT</context>
                    </properties>
                </deployable>
            </deployables>
        </configuration>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>install</goal>
                <goal>configure</goal>
                <goal>deploy</goal>
                <goal>package</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Notice that thist is actually only deploying the site application for Tomcat. We will manually copy over the admin.war in our startup.sh file referenced from the Procfile for our web dyno

One more Tomcat-specific item. Heroku assigns a random port number for your application, but Tomcat always defaults to 8080. To allow for this, I created a heroku-server.xml that the Heroku Procfile will rename to server.xml and put in the Tomcat deployment directory.

<?xml version='1.0' encoding='utf-8'?>
<!-- This is needed in order to tell Tomcat to start up on the dynamic port pass in via the Procfile -->
<Server port="-1"> 
    <Listener className="org.apache.catalina.core.JasperListener" /> 
    <Service name="Catalina"> 
        <!-- http.port will be assigned as an environment variable from the Procfile -->
        <Connector port="${http.port}" protocol="HTTP/1.1" connectionTimeout="20000"/> 
        <Engine name="Catalina" defaultHost="localhost"> 
            <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/> 
        </Engine>
    </Service> 
</Server>

Now the only thing that's left are the Heroku-specific files. The Procfile is extremely simple. All it does is kick off a custom shell script which does all the heavy lifting

Procfile:

web: sh startup-heroku.sh

The real meat comes from startup-heroku.sh:

# point to the correct configuration and webapp
CATALINA_BASE=`pwd`/site/target/cargo/configurations/tomcat7x
export CATALINA_BASE

#move the admin application to the tomcat deploy. Commented out because not enough memory on heroku free to deploy both applications
#mv ./admin/target/admin.war $CATALINA_BASE/webapps

#remove the extraneous webapps that we don't need
rm -rf $CATALINA_BASE/webapps/cargocpc*
rm -rf $CATALINA_BASE/host-manager
rm -rf $CATALINA_BASE/manager

#copy over the Heroku config files
cp ./site/heroku-server.xml $CATALINA_BASE/conf/server.xml

#make the Tomcat scripts executable
chmod a+x ./site/target/cargo/installs/apache-tomcat-7.0.29/apache-tomcat-7.0.29/bin/*.sh

#set the correct port and database settings
JAVA_OPTS="$JAVA_OPTS -XX:MaxPermSize=256M -Xmx256M -Dhttp.port=$PORT -DDATABASE_URL=$DATABASE_URL"
export JAVA_OPTS

#start Tomcat
./site/target/cargo/installs/apache-tomcat-7.0.29/apache-tomcat-7.0.29/bin/catalina.sh run

And that's it for the configuration! Now the only thing left is to start up the app!

If you haven't already, go ahead and create the Heroku cedar application which automatically adds a new git remote

~/broadleaf/heroku > heroku create --stack cedar
Creating glowing-river-8401... done, stack is cedar
http://glowing-river-8401.herokuapp.com/ | git@heroku.com:glowing-river-8401.git
Git remote heroku added 

Then just deploy with 'git push heroku' and you're done! To check for startup problems, you can tail the logs with heroku logs -tail which shows you the real-time logging info.