├── .gitignore
├── Procfile
├── README.md
├── license
├── pom.xml
├── src
├── main
│ ├── java
│ │ └── com
│ │ │ └── chrisbaileydeveloper
│ │ │ └── myapp
│ │ │ ├── Application.java
│ │ │ ├── ApplicationWebXml.java
│ │ │ ├── config
│ │ │ ├── Constants.java
│ │ │ ├── DatabaseConfiguration.java
│ │ │ ├── HerokuDatabaseConfiguration.java
│ │ │ ├── LocaleConfiguration.java
│ │ │ └── SecurityConfig.java
│ │ │ └── controller
│ │ │ ├── ErrorController.java
│ │ │ ├── HomeController.java
│ │ │ └── SigninController.java
│ └── resources
│ │ ├── config
│ │ ├── application-dev (PostgreSQL).yml
│ │ ├── application-dev.yml
│ │ ├── application-prod.yml
│ │ └── liquibase
│ │ │ ├── changelog
│ │ │ └── 00000000000000_initial_schema.xml
│ │ │ └── master.xml
│ │ ├── i18n
│ │ ├── application_en.properties
│ │ ├── application_fr.properties
│ │ ├── messages_en.properties
│ │ └── messages_fr.properties
│ │ ├── logback.xml
│ │ ├── static
│ │ ├── css
│ │ │ ├── bootstrap-theme.min.css
│ │ │ ├── bootstrap.min.css
│ │ │ └── main.css
│ │ └── js
│ │ │ ├── bootstrap.min.js
│ │ │ └── jquery.min.js
│ │ └── templates
│ │ ├── error.html
│ │ ├── fragments
│ │ ├── footer.html
│ │ └── header.html
│ │ ├── home
│ │ └── index.html
│ │ └── signin
│ │ └── signin.html
└── test
│ ├── java
│ └── com
│ │ └── chrisbaileydeveloper
│ │ └── myapp
│ │ └── controller
│ │ ├── HomeControllerTest.java
│ │ └── SigninControllerTest.java
│ └── resources
│ ├── config
│ └── application.yml
│ └── logback-test.xml
└── system.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | # Directories #
2 | /build/
3 | /bin/
4 | target/
5 |
6 | # OS Files #
7 | .DS_Store
8 |
9 | *.class
10 |
11 | # Package Files #
12 | *.jar
13 | *.war
14 | *.ear
15 | *.db
16 |
17 | ######################
18 | # Windows
19 | ######################
20 |
21 | # Windows image file caches
22 | Thumbs.db
23 |
24 | # Folder config file
25 | Desktop.ini
26 |
27 | ######################
28 | # OSX
29 | ######################
30 |
31 | .DS_Store
32 | .svn
33 |
34 | # Thumbnails
35 | ._*
36 |
37 | # Files that might appear on external disk
38 | .Spotlight-V100
39 | .Trashes
40 |
41 |
42 | ######################
43 | # Eclipse
44 | ######################
45 |
46 | *.pydevproject
47 | .project
48 | .metadata
49 | bin/**
50 | tmp/**
51 | tmp/**/*
52 | *.tmp
53 | *.bak
54 | *.swp
55 | *~.nib
56 | local.properties
57 | .classpath
58 | .settings/
59 | .loadpath
60 | /src/main/resources/rebel.xml
61 |
62 | # External tool builders
63 | .externalToolBuilders/
64 |
65 | # Locally stored "Eclipse launch configurations"
66 | *.launch
67 |
68 | # CDT-specific
69 | .cproject
70 |
71 | # PDT-specific
72 | .buildpath
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java -Xmx384m -Xss512k -XX:+UseCompressedOops -jar target/*.jar --spring.profiles.active=prod --server.port=$PORT --spring.datasource.heroku-url=$DATABASE_URL
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Spring Boot + Thymeleaf + Heroku Template
3 |
4 | This template has been designed to be used in conjunction with [JHipster](https://jhipster.github.io/) **version 2.6.0** to enable rapid development of Spring Boot + Thymeleaf applications that are fully deployable to the Heroku cloud.
5 |
6 | ### Technology Stack
7 | - Spring Boot, no-xml Spring MVC 4 web application for Servlet 3.0 environment
8 | - Thymeleaf templates with added Joda Time & Spring Security Dialects
9 | - Heroku fully cloud deployable
10 | - JPA 2.0 (Spring Data JPA/Hibernate)
11 | - Database (Liquibase/PostgreSQL/H2 embedded/HikariCP)
12 | - Testing (JUnit/Mockito/MockMVC/AssertJ/Hamcrest)
13 | - Java 8, Spring Security 3.2, Maven 3, SLF4J, Logback, Bootstrap 3.3.4, jQuery 1.11.2, i18n, etc
14 |
15 |
16 | ### Suggested Usage
17 | Utilize [JHipster](https://jhipster.github.io/) **version 2.6.0** to rapidly generate entities and Liquibase database changelogs that can then be transferred into this template.
18 |
19 | Entity classes can be transferred from JHipster's `domain` package. Liquibase changelogs can be transferred from JHipster's `src/main/resources/config/liquibase` folder.
20 |
21 | This template has been kept as lean as possible so that it can deploy successfully to Heroku without timing out while using an Heroku account with one allocated dyno.
22 |
23 | If interested in production-ready features, check out the [Spring Boot Actuator](http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#production-ready) which will add many useful tools with very little effort. Also, you can look at JHipster which is utilizing the [Metrics project](https://dropwizard.github.io/metrics/3.1.0/) as well as [Swagger](http://swagger.io/).
24 |
25 |
26 | ### JHipster Setup
27 | This template relies on [JHipster](https://jhipster.github.io/) **version 2.6.0**. In order to install version 2.6.0 please run the following command at the command prompt during your JHipster installation:
28 |
29 | ```
30 | $ npm install -g generator-jhipster@2.6.0
31 | ```
32 |
33 | Note that if you have a newer version of JHipster, this will override it.
34 |
35 | After the installation has completed, you can verify your JHipster version with the following command:
36 |
37 | ```
38 | npm list -g --depth=0 | grep jhipster
39 | ```
40 |
41 | While generating your [JHipster](https://jhipster.github.io/) application, select the following settings when prompted. The selected settings will allow for the highest level of integration between JHipster and this template.
42 |
43 | Do you want to use Java 8?
44 | **Yes (use Java 8)**
45 |
46 | Which \*type\* of authentication would you like to use?
47 | **HTTP Session Authentication**
48 |
49 | Which \*type\* of database would you like to use?
50 | **SQL (H2, MySQL, PostgreSQL)**
51 |
52 | Which \*production\* database would you like to use?
53 | **PostgreSQL**
54 |
55 | Which \*development\* database would you like to use?
56 | **H2 in-memory**
57 |
58 | Do you want to use Hibernate 2nd level cache?
59 | **No**
60 |
61 | Choose **Maven** as the build tool.
62 |
63 | ### Local Deployment
64 | ```
65 | $ mvn clean install
66 | $ mvn spring-boot:run
67 | ```
68 |
69 | Navigate to [http://localhost:8080](http://localhost:8080).
70 |
71 | The application can also be deployed by running the `Application.java` class.
72 |
73 | ### Deploying to Heroku
74 | The following steps require that the [Heroku Toolbelt](https://toolbelt.heroku.com/) has been installed locally and that a Heroku account has been created.
75 |
76 | Navigate to the project directory on the command line.
77 |
78 | Before creating your Heroku application, make sure that there is a Git repository associated with the project.
79 | ```
80 | $ git status
81 | ```
82 |
83 | If a Git repository is not associated with the project, then create one before continuing.
84 |
85 | Create a new application on Heroku
86 | ```
87 | $ heroku create
88 | ```
89 |
90 | Rename your Heroku application if interested
91 | ```
92 | $ heroku apps:rename new-name
93 | ```
94 |
95 | Add a PostgreSQL database to your Heroku application
96 | ```
97 | $ heroku addons:create heroku-postgresql
98 | ```
99 |
100 | Deploy project to Heroku
101 | ```
102 | $ git push heroku master
103 | ```
104 |
105 | Look at your application logs to see what is happening behind the scenes
106 | ```
107 | $ heroku logs
108 | ```
109 |
110 | If your application deploys without timing out then open it as follows
111 | ```
112 | $ heroku open
113 | ```
114 |
115 | If it takes longer than 60 seconds to build the application then Heroku will send a kill signal. If the application timed out while it was building then give it a few more minutes so that Heroku can attempt to load the application again.
116 |
117 | If your application times out while your Liquibase changes are being applied, then Liquibase will 'lock' the database. I have read about this occurring, but have not had to deal with it myself. I have tested out the following steps in a local development environment, but they have not yet been tested on Heroku.
118 |
119 | In order to manually unlock the database do the following:
120 |
121 | 1. In the `databasechangelog` table, remove the changelogs that did not execute properly.
122 |
123 |
124 | 1. In the `databasechangeloglock` table, set the locked value to false.
125 |
126 |
127 | 1. Modify the database to the state it was in before the changelogs. For example, if your changelog added a table called `T_Employee` then remove the `T_Employee` table from the database before attempting to redeploy your application to Heroku.
128 |
129 | ### Updating your Heroku application
130 | After making changes to your project, and updating your Git repository with those changes, you can push those changes to Heroku as follows:
131 |
132 | ```
133 | $ git push heroku master
134 | ```
135 |
136 | ### Deploying to a new Heroku dyno
137 | If for any reason you are interested in starting from scratch with a new Heroku application, you can do the following:
138 |
139 | ```
140 | $ git remote rm heroku
141 | ```
142 |
143 | You can then start from scratch with the `heroku create` command.
144 |
145 | ### Template Customizations
146 | In addition to the renaming of the template's packages, there are a few specific locations that should also be modified. They are as follows:
147 |
148 | Modify the `DatabaseConfiguration.java` class so that the following line contains your package name:
149 | ```
150 | @EnableJpaRepositories("*com.chrisbaileydeveloper.myapp.repository")
151 | ```
152 |
153 | Modify the `src/main/resources/logback.xml` file so that the following line contains your package name:
154 | ```
155 |
158 | Modify the `src/test/resources/logback-test.xml` file so that the following line contains your package name:
159 | ```
160 |
163 | Modify the `pom.xml` file so that the following line contains your package name:
164 | `
28 | * Please use -Dspring.profiles.active=dev 29 | *
30 | */ 31 | private String addDefaultProfile() { 32 | String profile = System.getProperty("spring.profiles.active"); 33 | if (profile != null) { 34 | log.info("Running with Spring profile(s) : {}", profile); 35 | return profile; 36 | } 37 | 38 | log.warn("No Spring profile configured, running with default configuration"); 39 | return Constants.SPRING_PROFILE_DEVELOPMENT; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/chrisbaileydeveloper/myapp/config/Constants.java: -------------------------------------------------------------------------------- 1 | package com.chrisbaileydeveloper.myapp.config; 2 | 3 | /** 4 | * Application constants. 5 | */ 6 | public final class Constants { 7 | 8 | private Constants() { 9 | } 10 | 11 | public static final String SPRING_PROFILE_DEVELOPMENT = "dev"; 12 | public static final String SPRING_PROFILE_PRODUCTION = "prod"; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/chrisbaileydeveloper/myapp/config/DatabaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.chrisbaileydeveloper.myapp.config; 2 | 3 | import java.util.Arrays; 4 | 5 | import javax.sql.DataSource; 6 | 7 | import liquibase.integration.spring.SpringLiquibase; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.bind.RelaxedPropertyResolver; 12 | import org.springframework.context.ApplicationContextException; 13 | import org.springframework.context.EnvironmentAware; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Profile; 17 | import org.springframework.core.env.Environment; 18 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 19 | import org.springframework.transaction.annotation.EnableTransactionManagement; 20 | 21 | import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module; 22 | import com.zaxxer.hikari.HikariConfig; 23 | import com.zaxxer.hikari.HikariDataSource; 24 | 25 | @Configuration 26 | @EnableJpaRepositories("com.chrisbaileydeveloper.myapp.repository") 27 | @EnableTransactionManagement 28 | public class DatabaseConfiguration implements EnvironmentAware{ 29 | private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class); 30 | 31 | private RelaxedPropertyResolver propertyResolver; 32 | 33 | private Environment env; 34 | 35 | @Override 36 | public void setEnvironment(Environment env) { 37 | this.env = env; 38 | this.propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource."); 39 | } 40 | 41 | @Bean 42 | @Profile(Constants.SPRING_PROFILE_DEVELOPMENT) 43 | public DataSource dataSource() { 44 | log.debug("Configuring Datasource"); 45 | if (propertyResolver.getProperty("url") == null && propertyResolver.getProperty("databaseName") == null) { 46 | log.error("Your database connection pool configuration is incorrect! The application" + 47 | "cannot start. Please check your Spring profile, current profiles are: {}", 48 | Arrays.toString(env.getActiveProfiles())); 49 | 50 | throw new ApplicationContextException("Database connection pool is not configured correctly"); 51 | } 52 | HikariConfig config = new HikariConfig(); 53 | config.setDataSourceClassName(propertyResolver.getProperty("dataSourceClassName")); 54 | if (propertyResolver.getProperty("url") == null || "".equals(propertyResolver.getProperty("url"))) { 55 | config.addDataSourceProperty("databaseName", propertyResolver.getProperty("databaseName")); 56 | config.addDataSourceProperty("serverName", propertyResolver.getProperty("serverName")); 57 | } else { 58 | config.addDataSourceProperty("url", propertyResolver.getProperty("url")); 59 | } 60 | config.addDataSourceProperty("user", propertyResolver.getProperty("username")); 61 | config.addDataSourceProperty("password", propertyResolver.getProperty("password")); 62 | 63 | return new HikariDataSource(config); 64 | } 65 | 66 | /* Liquibase */ 67 | @Bean 68 | public SpringLiquibase liquibase(DataSource dataSource) { 69 | SpringLiquibase liquibase = new SpringLiquibase(); 70 | liquibase.setDataSource(dataSource); 71 | liquibase.setChangeLog("classpath:config/liquibase/master.xml"); 72 | liquibase.setContexts("development, production"); 73 | liquibase.setShouldRun(true); 74 | 75 | log.debug("Configuring Liquibase"); 76 | return liquibase; 77 | } 78 | 79 | @Bean 80 | public Hibernate4Module hibernate4Module() { 81 | return new Hibernate4Module(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/chrisbaileydeveloper/myapp/config/HerokuDatabaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.chrisbaileydeveloper.myapp.config; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | 6 | import javax.sql.DataSource; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.boot.bind.RelaxedPropertyResolver; 11 | import org.springframework.context.ApplicationContextException; 12 | import org.springframework.context.EnvironmentAware; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Profile; 16 | import org.springframework.core.env.Environment; 17 | 18 | import com.zaxxer.hikari.HikariConfig; 19 | import com.zaxxer.hikari.HikariDataSource; 20 | 21 | @Configuration 22 | public class HerokuDatabaseConfiguration implements EnvironmentAware { 23 | 24 | private final Logger log = LoggerFactory 25 | .getLogger(HerokuDatabaseConfiguration.class); 26 | 27 | private RelaxedPropertyResolver propertyResolver; 28 | 29 | @Override 30 | public void setEnvironment(Environment environment) { 31 | this.propertyResolver = new RelaxedPropertyResolver(environment, 32 | "spring.datasource."); 33 | } 34 | 35 | @Bean 36 | @Profile(Constants.SPRING_PROFILE_PRODUCTION) 37 | public DataSource dataSource() { 38 | log.debug("Configuring Heroku Datasource"); 39 | 40 | String herokuUrl = propertyResolver.getProperty("heroku-url"); 41 | if (herokuUrl != null) { 42 | log.info("Using Heroku, parsing their $DATABASE_URL to use it with JDBC"); 43 | URI dbUri = null; 44 | try { 45 | dbUri = new URI(herokuUrl); 46 | } catch (URISyntaxException e) { 47 | throw new ApplicationContextException( 48 | "Heroku database connection pool is not configured correctly"); 49 | } 50 | String username = dbUri.getUserInfo().split(":")[0]; 51 | String password = dbUri.getUserInfo().split(":")[1]; 52 | String dbUrl = "jdbc:postgresql://" 53 | + dbUri.getHost() 54 | + ':' 55 | + dbUri.getPort() 56 | + dbUri.getPath() 57 | + "?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory"; 58 | 59 | HikariConfig config = new HikariConfig(); 60 | config.setDataSourceClassName(propertyResolver 61 | .getProperty("dataSourceClassName")); 62 | config.addDataSourceProperty("url", dbUrl); 63 | config.addDataSourceProperty("user", username); 64 | config.addDataSourceProperty("password", password); 65 | return new HikariDataSource(config); 66 | } else { 67 | throw new ApplicationContextException( 68 | "Heroku database URL is not configured, you must set --spring.datasource.heroku-url=$DATABASE_URL"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/chrisbaileydeveloper/myapp/config/LocaleConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.chrisbaileydeveloper.myapp.config; 2 | 3 | import java.util.Locale; 4 | 5 | import org.springframework.context.MessageSource; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.support.ReloadableResourceBundleMessageSource; 9 | import org.springframework.web.servlet.LocaleResolver; 10 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 12 | import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 13 | import org.springframework.web.servlet.i18n.SessionLocaleResolver; 14 | import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect; 15 | 16 | import uk.co.gcwilliams.jodatime.thymeleaf.JodaTimeDialect; 17 | 18 | @Configuration 19 | public class LocaleConfiguration extends WebMvcConfigurerAdapter { 20 | 21 | /** 22 | * Thymeleaf LocaleResolver 23 | */ 24 | @Bean (name = "localeResolver") 25 | public LocaleResolver localeResolver() { 26 | SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver(); 27 | sessionLocaleResolver.setDefaultLocale(Locale.CANADA); 28 | return sessionLocaleResolver; 29 | } 30 | 31 | @Override 32 | public void addInterceptors(InterceptorRegistry registry) { 33 | LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); 34 | localeChangeInterceptor.setParamName("lang"); 35 | registry.addInterceptor(localeChangeInterceptor); 36 | } 37 | 38 | @Bean 39 | public MessageSource messageSource() { 40 | ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 41 | messageSource.setBasenames("classpath:/i18n/messages", "classpath:/i18n/application"); 42 | messageSource.setDefaultEncoding("UTF-8"); 43 | return messageSource; 44 | } 45 | 46 | /** 47 | * Joda Time to Thymeleaf convertert |
java.lang.NullPointerException
10 | 11 |Sorry, an error has occurred.
14 | 15 | Status: ()26 | This template has been designed to be used in conjunction with JHipster to enable rapid development of Spring Boot + 28 | Thymeleaf applications that are fully deployable to Heroku. 29 |
30 |You are currently signed in as an anonymous user.
47 |You are currently signed in as a user.
51 |You are currently signed in as the administrator.
55 |
42 | User account = user & user
43 | Admin account = admin & admin
44 |