= SpringBoot =

== Example ==

=== pom.xml ===
{{{#!highlight xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>hello</groupId>
    <artifactId>test-spring-boot</artifactId>
    <version>0.1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>1.4.4</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>

    </dependencies>
    <properties>
        <start-class>hello.Application</start-class>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
}}}

=== src/main/java/hello/GreetingController.java ===
{{{#!highlight java
package hello;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.ArrayList;

@Controller
public class GreetingController {
    private final Logger logger = LoggerFactory.getLogger(GreetingController.class);
    @Autowired
    DummyDAO dummyDAO;

    public GreetingController(){
        logger.debug("Greeting controller created.");
    }

    @RequestMapping("/greeting")
    // http://localhost:8080/greeting?name=nnnn 
    public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
        logger.info("Greeting endpoint called.");
        model.addAttribute("name", name);
        return "greeting";
    }

    @RequestMapping(value="/dummy",produces="application/json")
    @ResponseBody
    // http://localhost:8080/dummy
    public List<Dummy> dummy(){
      List<Dummy> list= new java.util.ArrayList<Dummy>();
      Dummy dummy = new Dummy();
      dummy.setFieldA("AAA");
      dummy.setFieldB("CCC");
      list.add(dummy);

      Dummy dummy2 = new Dummy();
      dummy2.setFieldA("AAA2");
      dummy2.setFieldB("CCC2");
      list.add(dummy2);

      return list;
    }

    @RequestMapping(value="/dummyname",produces="application/json")
    @ResponseBody
    // http://localhost:8080/dummyname
    public String getDummyName(){
      return dummyDAO.getNameFromDummy();
    }


}
}}}

=== src/main/java/hello/Application.java ===
{{{#!highlight java
package hello;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ComponentScan
@EnableAutoConfiguration
public class Application {
    private static Logger logger;

    public static void main(String[] args) {
        logger = LoggerFactory.getLogger(Application.class);
        logger.info("Starting application");
        SpringApplication.run(Application.class, args);
    }

    @Bean(name="basicDataSource",destroyMethod="close")
    public BasicDataSource createBasicDataSource(){
      logger.info("Creating basicDataSource");
      BasicDataSource bds = new BasicDataSource();
      bds.setDriverClassName("org.mariadb.jdbc.Driver");
      bds.setUrl("jdbc:mariadb://localhost:3306/springmvchtml");
      bds.setUsername("usertest");
      bds.setPassword("usertest");
      bds.setInitialSize(3);
      return bds;
    }
/*
    @Bean(name="jdbcTemplate")
    public JdbcTemplate jdbcTemplate(){
      logger.info("Creating jdbcTemplate");
      return new JdbcTemplate( basicDataSource() );
    }
*/
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Autowired @Qualifier("basicDataSource") BasicDataSource bds  ){
      logger.info("Creating jdbcTemplate");
      return new JdbcTemplate( bds );
    }

}
}}}

=== src/main/java/hello/ThreadTimer.java ===
{{{#!highlight java
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;

@Component
public class ThreadTimer extends Thread {
  private int delaySeconds;
  private Logger logger;
  private boolean running;
  private Object monitor=new Object();
  private ArrayList<Object> subscribers;

  //@Autowired
  //WaitThread waitThread;

  public ThreadTimer() {
    this.logger = LoggerFactory.getLogger(ThreadTimer.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.delaySeconds = 5 * 1000;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.subscribers = new ArrayList<Object>();
  }

  public void addSubscriber(Object subscriber){
    this.subscribers.add(subscriber);
  }

  @PostConstruct
  public void init() {
    logger.info("Starting the thread");
    this.start();
  }

  @Override
  public void run() {
    while (running) {
      try {
        Thread.sleep(this.delaySeconds);
        logger.info("Delay " + this.getClass().getSimpleName());

        for(Object o: this.subscribers){
          synchronized(o){
            o.notify();
          }
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          e.printStackTrace();
          logger.info("ThreadTimer exception:" + e.getMessage() );
          stopRunning();
      }
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }

  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();
  }
}

}}}

=== src/main/java/hello/WaitThread.java ===
{{{#!highlight java
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class WaitThread extends Thread {
  private Logger logger;
  private boolean running;
  private Object monitor;

  @Autowired
  ThreadTimer timerThread;

  public WaitThread() {
    this.logger = LoggerFactory.getLogger(WaitThread.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.monitor=new Object();
  }

  @PostConstruct
  public void init() {
    this.timerThread.addSubscriber(this);
    logger.info("Starting the thread");
    this.start();
  }

  public void run() {
    while (running) {
      try {
        synchronized(this){
          this.wait();
          logger.info("Notification received.");
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          logger.info("WaitThread exception:" + e.getMessage() );
          stopRunning();
      }
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }

  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();
  }
}
}}}


=== src/main/java/hello/Dummy.java ===
{{{#!highlight java
package hello;

public class Dummy{
  private String fieldA;
  private String fieldB;

  public Dummy(){
  }

  public String getFieldA(){
    return fieldA;
  }

  public String getFieldB(){
    return fieldB;
  }

  public void setFieldA(String arg){
    fieldA = arg;
  }

  public void setFieldB(String arg){
    fieldB = arg;
  }
}
}}}

=== src/main/java/hello/DummyDAO.java ===
{{{#!highlight java
package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class DummyDAO {
  @Autowired
  @Qualifier("jdbcTemplate")
  private JdbcTemplate jdbcTemplate;
  private Logger logger;

  public DummyDAO() {
    logger = LoggerFactory.getLogger(DummyDAO.class);
    logger.info("Created " + this.getClass().getSimpleName());
  }

  public String getNameFromDummy() {
    return this.jdbcTemplate.queryForObject("select name from dummy limit 1", String.class);
  }
}
}}}

=== src/main/resources/templates/greeting.html ===
{{{#!highlight html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
}}}

=== src/main/resources/application.properties ===
{{{#!highlight sh
logging.file=/tmp/testout.log
}}}

=== src/main/resources/logback-spring.xml ===
{{{#!highlight xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
-->
    <!-- override spring logback default behaviour -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${filelog}</file>
        <encoder>
                <pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="GREETFILE" class="ch.qos.logback.core.FileAppender">
        <file>/tmp/greet.log</file>
        <encoder>
                <pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
                <Pattern>%yellow(%date{ISO8601}) %green([%thread]) %highlight(%-5level) %cyan(%logger{35}) - %white(%msg%n) </Pattern>
        </layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE" />
        <appender-ref ref="CONSOLE"/>
    </root>

    <logger name="hello.GreetingController" level="debug" additivity="false">
        <appender-ref ref="GREETFILE"/>
        <appender-ref ref="CONSOLE" />
    </logger>

</configuration>
}}}

=== src/main/resources/test.sql ===
{{{#!highlight sql
--JDBC URL: jdbc:mariadb://localhost:3306/springmvchtml
--mysql -u root -p
create database springmvchtml;
create user 'usertest'@'%' identified by '????????';
create user 'usertest'@'localhost' identified by '????????';
grant all on springmvchtml.* to 'usertest'@'%';
grant all on springmvchtml.* to 'usertest'@'localhost';
show grants for 'usertest'@'%';
show grants for 'usertest'@'localhost';
create table springmvchtml.dummy (name varchar(255) ) ;
insert into springmvchtml.dummy (name) values('aaaa');
insert into springmvchtml.dummy (name) values('bbbb');
commit;
-- mysql -u usertest -p
}}}

=== JAR files list ===
 * jar tf target/test-spring-boot-0.1.0.jar | grep "\.jar"
{{{
BOOT-INF/lib/jackson-core-2.8.1.jar
BOOT-INF/lib/spring-web-4.3.2.RELEASE.jar
BOOT-INF/lib/unbescape-1.1.0.RELEASE.jar
BOOT-INF/lib/javassist-3.20.0-GA.jar
BOOT-INF/lib/mariadb-java-client-1.4.4.jar
BOOT-INF/lib/jul-to-slf4j-1.7.21.jar
BOOT-INF/lib/groovy-2.4.7.jar
BOOT-INF/lib/hibernate-validator-5.2.4.Final.jar
BOOT-INF/lib/classmate-1.3.1.jar
BOOT-INF/lib/tomcat-embed-el-8.5.4.jar
BOOT-INF/lib/tomcat-embed-core-8.5.4.jar
BOOT-INF/lib/commons-pool-1.6.jar
BOOT-INF/lib/thymeleaf-spring4-2.1.5.RELEASE.jar
BOOT-INF/lib/spring-context-4.3.2.RELEASE.jar
BOOT-INF/lib/thymeleaf-2.1.5.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-tomcat-1.4.0.RELEASE.jar
BOOT-INF/lib/thymeleaf-layout-dialect-1.4.0.jar
BOOT-INF/lib/spring-boot-autoconfigure-1.4.0.RELEASE.jar
BOOT-INF/lib/snakeyaml-1.17.jar
BOOT-INF/lib/spring-boot-starter-web-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-logging-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-webmvc-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-beans-4.3.2.RELEASE.jar
BOOT-INF/lib/slf4j-api-1.7.21.jar
BOOT-INF/lib/spring-jdbc-4.3.2.RELEASE.jar
BOOT-INF/lib/jackson-annotations-2.8.1.jar
BOOT-INF/lib/commons-dbcp-1.4.jar
BOOT-INF/lib/ognl-3.0.8.jar
BOOT-INF/lib/spring-boot-starter-1.4.0.RELEASE.jar
BOOT-INF/lib/tomcat-embed-websocket-8.5.4.jar
BOOT-INF/lib/spring-boot-starter-thymeleaf-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-tx-4.3.2.RELEASE.jar
BOOT-INF/lib/logback-classic-1.1.7.jar
BOOT-INF/lib/spring-boot-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-expression-4.3.2.RELEASE.jar
BOOT-INF/lib/logback-core-1.1.7.jar
BOOT-INF/lib/validation-api-1.1.0.Final.jar
BOOT-INF/lib/spring-core-4.3.2.RELEASE.jar
BOOT-INF/lib/jackson-databind-2.8.1.jar
BOOT-INF/lib/jcl-over-slf4j-1.7.21.jar
BOOT-INF/lib/log4j-over-slf4j-1.7.21.jar
BOOT-INF/lib/spring-aop-4.3.2.RELEASE.jar
BOOT-INF/lib/jboss-logging-3.3.0.Final.jar
}}}

== Add HTTPS/SSL support ==
Keystore creation
{{{
#create keystore with key springboot
keytool -genkey -alias springboot -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore springboot.p12 -validity 3650

Enter keystore password: 12345678
Re-enter new password: 12345678
What is your first and last name?
  [Unknown]:  Spring Boot
What is the name of your organizational unit?
  [Unknown]:  Boot
What is the name of your organization?
  [Unknown]:  Boot
What is the name of your City or Locality?
  [Unknown]:  Lisbon
What is the name of your State or Province?
  [Unknown]:  Estremadura
What is the two-letter country code for this unit?
  [Unknown]:  PT
Is CN=Spring Boot, OU=Boot, O=Boot, L=Lisbon, ST=Estremadura, C=PT correct?
  [no]:  yes

#list keys 
keytool -list -v -keystore springboot.p12 -storetype pkcs12  
}}}

Code to add in Application class/configuration
{{{#!highlight java
// import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
// import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
// import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
// import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
// import java.io.FileNotFoundException;
// import org.apache.catalina.connector.Connector;
// import org.springframework.beans.factory.annotation.Value;
// import javax.inject.Inject;
// import org.springframework.util.ResourceUtils;

public class TCC implements TomcatConnectorCustomizer{
    private String keystoreFile;
    private String keystorePassword;
    private String keystoreType;
    private String keystoreAlias;

    public TCC(String keystoreFile, String keystorePassword, String keystoreType, String keystoreAlias){
        this.keystoreFile=keystoreFile;
        this.keystorePassword=keystorePassword;
        this.keystoreType=keystoreType;
        this.keystoreAlias=keystoreAlias;
    }

    public void customize(Connector connector){
        connector.setSecure(true);
        connector.setScheme("https");
        connector.setAttribute("keystoreFile", keystoreFile);
        connector.setAttribute("keystorePass", keystorePassword);
        connector.setAttribute("keystoreType", keystoreType);
        connector.setAttribute("keyAlias", keystoreAlias);
        connector.setAttribute("clientAuth", "false");
        connector.setAttribute("sslProtocol", "TLS");
        connector.setAttribute("SSLEnabled", true);
    }
}

public class ESCC implements EmbeddedServletContainerCustomizer{
    private String keystoreFile;
    private String keystorePassword;
    private String keystoreType;
    private String keystoreAlias;

    public ESCC(String keystoreFile, String keystorePassword, String keystoreType, String keystoreAlias){
        this.keystoreFile=keystoreFile;
        this.keystorePassword=keystorePassword;
        this.keystoreType=keystoreType;
        this.keystoreAlias=keystoreAlias;
    }

    public void customize(ConfigurableEmbeddedServletContainer container){
        TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;

        TomcatConnectorCustomizer tcc = new TCC(keystoreFile,keystorePassword,keystoreType,keystoreAlias);
        containerFactory.addConnectorCustomizers(tcc);
    }
}

@Bean
@Inject
public EmbeddedServletContainerCustomizer containerCustomizer(
    @Value("${keystore.file}") String keystoreFile,
    @Value("${keystore.password}") String keystorePassword,
    @Value("${keystore.type}") String keystoreType,
    @Value("${keystore.alias}") String keystoreAlias) throws FileNotFoundException
{
    final String absoluteKeystoreFile = ResourceUtils.getFile(keystoreFile).getAbsolutePath();
    EmbeddedServletContainerCustomizer escc = new ESCC(absoluteKeystoreFile,keystorePassword,keystoreType,keystoreAlias);
    return escc;
}
}}}

pom.xml 
{{{#!highlight xml
<!-- Add dependency to support @Inject annotation -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
}}}
 * Add server.port=8443 to application.properties or use system property -Dserver.port=8443
 * Run java -Dfilelog=/tmp/out.log -Dkeystore.file=springboot.p12 -Dkeystore.password=12345678 -Dkeystore.type=PKCS12 -Dkeystore.alias=springboot -jar target/test-spring-boot-0.1.0.jar
 * Access https://localhost:8443/greeting?name=x

== Environment variables ==

src/main/resources/application.properties
{{{
logging.file=/tmp/testout.log
server.port=8443
foo.bar=bohica
}}}

{{{#!highlight java
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Value;
//....................
    @Resource
    Environment env;
    @Value("${foo.bar}")
    private String fooBar;

    @RequestMapping(value="/env",produces="application/json")
    @ResponseBody
    public List<String> getEnv(){
      List<String> ret = new ArrayList<String>();
      ret.add( env.getProperty("foo.bar") );
      ret.add(fooBar);
      return ret;
    }
//....................
}}}
Override value in application.properties
 *  java -jar target/test-spring-boot-0.1.0.jar --foo.bar=snafu

Instead of using @Autowired or @Inject use @Resource (javax.annotation.Resource).

== Example JPA RabbitMQ HttpSession Redis ==
Structure
{{{
.
./pom.xml
./src
./src/main
./src/main/java
./src/main/java/hello
./src/main/java/hello/GreetingController.java
./src/main/java/hello/Application.java
./src/main/java/hello/ThreadTimer.java
./src/main/java/hello/WaitThread.java
./src/main/java/hello/Dummy.java
./src/main/java/hello/DummyDAO.java
./src/main/java/hello/DummyJPA.java
./src/main/java/hello/MessageHandler.java
./src/main/java/hello/BroadcastMessageHandler.java
./src/main/java/hello/ConfigHttpSession.java
./src/main/resources
./src/main/resources/templates
./src/main/resources/templates/greeting.html
./src/main/resources/application.properties
./src/main/resources/logback-spring.xml
./src/main/resources/test.sql
./src/main/resources/META-INF
./src/main/resources/META-INF/persistence.xml
}}}

WaitThread.java
{{{
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class WaitThread extends Thread {
  private Logger logger;
  private boolean running;
  private Object monitor;

  @Autowired
  ThreadTimer timerThread;

  public WaitThread() {    
    this.logger = LoggerFactory.getLogger(WaitThread.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.monitor=new Object();
  }

  @PostConstruct
  public void init() {
    this.timerThread.addSubscriber(this);
    logger.info("Starting the thread");
    this.start();
  }

  public void run() {
    while (running) {
      try {
        synchronized(this){
          this.wait();
          logger.info("Notification received.");
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          logger.info("WaitThread exception:" + e.getMessage() );
          stopRunning();
      }      
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }
  
  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();    
  }
}
}}}

ThreadTimer.java
{{{
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;

@Component
public class ThreadTimer extends Thread {
  private int delaySeconds;
  private Logger logger;
  private boolean running;
  private Object monitor=new Object();
  private ArrayList<Object> subscribers;

  public ThreadTimer() {    
    this.logger = LoggerFactory.getLogger(ThreadTimer.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.delaySeconds = 5 * 1000;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.subscribers = new ArrayList<Object>();
  }

  public void addSubscriber(Object subscriber){
    this.subscribers.add(subscriber);
  }

  @PostConstruct
  public void init() {
    logger.info("Starting the thread");
    this.start();
  }

  @Override
  public void run() {
    while (running) {
      try {
        Thread.sleep(this.delaySeconds);
        logger.info("Delay " + this.getClass().getSimpleName());

        for(Object o: this.subscribers){
          synchronized(o){
            o.notify();
          }
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          e.printStackTrace();
          logger.info("ThreadTimer exception:" + e.getMessage() );
          stopRunning();
      }      
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }
  
  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();    
  }
}
}}}

MessageHandler.java
{{{
package hello;

import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MessageHandler{
    private final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
    
    public void handleMessage(byte[] message){
        String msg = new String(message, StandardCharsets.UTF_8);
        logger.info("Received msg " + msg   );
    }

    public void handleMessage(String message){
        logger.info("Received msg " + message   );
    }

}
}}}

GreetingController.java
{{{
package hello;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.ArrayList;
import org.springframework.web.bind.annotation.PathVariable;
//import javax.inject.Inject;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.Resource;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import javax.servlet.http.HttpSession;

@Controller
public class GreetingController {
    private final Logger logger = LoggerFactory.getLogger(GreetingController.class);
    @Resource
    DummyDAO dummyDAO;

    @Resource
    Environment env;

    @Resource
    RabbitTemplate rabbitTemplate;
 
    @Value("${instance.name}")
    String instanceName;
    
    public GreetingController(){
        logger.debug("Greeting controller created.");
    }

    @RequestMapping("/greeting")
    public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model  ) {        
        model.addAttribute("name", name);
        rabbitTemplate.convertAndSend("topicExchange","myQueue", name  );
        rabbitTemplate.convertAndSend("fanoutExchange","", name  );
        return "greeting";
    }

    @RequestMapping("/setvalue/{value}")
    @ResponseBody
    public String setValue(@PathVariable("value") String value, Model model,HttpSession httpSession) {
        httpSession.setAttribute("name", value );
        return httpSession.getId();
    }

    @RequestMapping("/getvalue")
    @ResponseBody
    public String setValue(Model model,HttpSession httpSession) {
        return (String) httpSession.getAttribute("name");        
    }
    
    @RequestMapping("/instancename")
    @ResponseBody
    public String setValue(Model model) {
        return this.instanceName;        
    }
    
    @RequestMapping("/greeting/add/{name}")
    public String addGreeting(@PathVariable("name") String name, Model model) {
        model.addAttribute("name", name);
        dummyDAO.addName(name);
        return "greeting";
    }
    
    @RequestMapping(value="/dummy",produces="application/json")
    @ResponseBody
    public List<DummyJPA> dummy(){
      return dummyDAO.getAllNames();
    }

    @RequestMapping(value="/dummyname",produces="application/json")
    @ResponseBody
    public String getDummyName(){
      return dummyDAO.getNameFromDummy();
    }

    @Value("${foo.bar}")
    private String fooBar;

    @RequestMapping(value="/env",produces="application/json")
    @ResponseBody
    public List<String> getEnv(){
      List<String> ret = new ArrayList<String>(); 
      ret.add( env.getProperty("foo.bar") );
      ret.add(fooBar);
      return ret;
    }
    
}
}}}

DummyJPA.java
{{{
package hello;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.NamedQueries;

@Entity
@NamedQueries({
    @NamedQuery(name="DummyJPA.getAll",  query="Select e From DummyJPA e")
})
public class DummyJPA{
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;
  private String dummy;

  public DummyJPA(){
  } 

  public String getDummy(){
    return this.dummy;
  }

  public void setDummy(String dummy){
    this.dummy=dummy;
  }

  @Override
  public String toString(){
    return String.format("Dummy %s", this.dummy);
  }
}
}}}

DummyDAO.java
{{{
package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.Transactional;
import java.util.List;

@Component
public class DummyDAO {
  @Autowired
  @Qualifier("jdbcTemplate")
  private JdbcTemplate jdbcTemplate;
  private Logger logger;

  @PersistenceContext(unitName="default")
  EntityManager em;

  public DummyDAO() {
    logger = LoggerFactory.getLogger(DummyDAO.class);
    logger.info("Created " + this.getClass().getSimpleName());
  }

  public String getNameFromDummy() {
    return this.jdbcTemplate.queryForObject("select name from dummy limit 1", String.class);
  }

  @Transactional
  public void addName(String name){
    DummyJPA djpa = new DummyJPA();
    djpa.setDummy(name);
    em.persist(djpa);
  }

  @Transactional
  public List<DummyJPA> getAllNames(){
    return em.createNamedQuery("DummyJPA.getAll",DummyJPA.class).getResultList();
  }

}
}}}

Dummy.java
{{{
package hello;

public class Dummy{
  private String fieldA;
  private String fieldB;
  
  public Dummy(){
  }

  public String getFieldA(){
    return fieldA;
  }
  
  public String getFieldB(){
    return fieldB;
  }
  
  public void setFieldA(String arg){
    fieldA = arg;
  }
  
  public void setFieldB(String arg){
    fieldB = arg;
  }
}

}}}

ConfigHttpSession.java
{{{
/* 
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot.html

pkg install redis #freebsd
add redis_enable="YES" to /etc/rc.conf
/usr/local/etc/rc.d/redis start

/usr/local/etc/redis.conf
requirepass 12345678

keys *
hgetall <hashkey>

Same as in application.properties spring.redis.password=12345678

https://localhost:8443/setvalue/valx
147569e4-9e6d-4363-a373-b20261513f11

https://localhost:8443/getvalue
SESSION=147569e4-9e6d-4363-a373-b20261513f11

2 instances of spring boot using the same redis server
curl -k -v --cookie "SESSION=147569e4-9e6d-4363-a373-b20261513f11" https://localhost:8443/getvalue
curl -k -v --cookie "SESSION=147569e4-9e6d-4363-a373-b20261513f11" https://localhost:8444/getvalue 
*/
package hello;

import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@EnableRedisHttpSession
public class ConfigHttpSession{
    // springSessionRepositoryFilterbean is responsible for replacing the HttpSession with a custom implementation that is backed by Spring Session.    
}

}}}

BroadcastMessageHandler.java
{{{
package hello;

import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BroadcastMessageHandler{
    private final Logger logger = LoggerFactory.getLogger(BroadcastMessageHandler.class);
    
    public void handleMessage(byte[] message){
        String msg = new String(message, StandardCharsets.UTF_8);
        logger.info("Received msg " + msg   );
    }

    public void handleMessage(String message){
        logger.info("Received msg " + message   );
    }
}
}}}

Application.java
{{{
package hello;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import java.io.FileNotFoundException;
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import javax.inject.Inject;
import javax.annotation.Resource;
import org.springframework.util.ResourceUtils;
import org.springframework.core.env.Environment;

//-- rabbit
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.AnonymousQueue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import java.net.URI;
//-- rabbit

@ComponentScan
@EnableAutoConfiguration
@EntityScan(basePackages="hello")
public class Application {
    @Resource
    Environment env;

    private static Logger logger;

    public static void main(String[] args) {
        logger = LoggerFactory.getLogger(Application.class);
        logger.info("Starting application");
        SpringApplication.run(Application.class, args);
    }

    @Bean(name="basicDataSource",destroyMethod="close") 
    public BasicDataSource createBasicDataSource(){
      logger.info("Creating basicDataSource");
      BasicDataSource bds = new BasicDataSource();
      // get config data from application.properties
      bds.setDriverClassName(env.getProperty("db.driverclass") );
      bds.setUrl(env.getProperty("db.jdbcurl") );
      bds.setUsername( env.getProperty("db.username") );
      bds.setPassword( env.getProperty("db.password") );
      bds.setInitialSize( Integer.parseInt( env.getProperty("db.initialsizeconnpool")) );
      return bds;
    }

    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Autowired @Qualifier("basicDataSource") BasicDataSource bds  ){
      logger.info("Creating jdbcTemplate");
      return new JdbcTemplate( bds );
    }

//---  
    public class TCC implements TomcatConnectorCustomizer{
        Environment env;

        public TCC(Environment env){ 
            this.env=env;  
        }  

        public void customize(Connector connector){
            String absoluteKeystoreFile = "";
            logger.info(String.format("Keystore file: %s" , env.getProperty("keystore.file")   )); 

            try{ 
                absoluteKeystoreFile = ResourceUtils.getFile(env.getProperty("keystore.file")).getAbsolutePath(); 
                logger.info(String.format("Absolute path keystore: %s" , absoluteKeystoreFile )  );
            }
            catch(Exception ex){
              logger.error( ex.getMessage() );
            }

            connector.setSecure(true);
            connector.setScheme("https");
            connector.setAttribute("keystoreFile", absoluteKeystoreFile);
            connector.setAttribute("keystorePass", env.getProperty("keystore.password"));
            connector.setAttribute("keystoreType", env.getProperty("keystore.type"));
            connector.setAttribute("keyAlias", env.getProperty("keystore.alias")  );
            connector.setAttribute("clientAuth", "false");
            connector.setAttribute("sslProtocol", "TLS");
            connector.setAttribute("SSLEnabled", true);
        } 
    }

    public class ESCC implements EmbeddedServletContainerCustomizer{
        Environment env;

        public ESCC(Environment env){ 
            this.env=env;  
        }

        public void customize(ConfigurableEmbeddedServletContainer container){
            TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;            
            TomcatConnectorCustomizer tcc = new TCC(env);
            containerFactory.addConnectorCustomizers(tcc);
        }
    }

    @Bean(name="containerCustomizer")
    public EmbeddedServletContainerCustomizer containerCustomizer() 
    throws FileNotFoundException {
        logger.info("Creating containerCustomizer");
        EmbeddedServletContainerCustomizer escc = new ESCC(env);
        return escc;
    }
//---

//---- rabbit

//import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
    @Bean(name="cachingConnectionFactory")
    CachingConnectionFactory createCachingConnectionFactory(){
        logger.info("Creating cachingConnectionFactory");
        String uri = env.getProperty("rabbitmq.uri");
        URI rabbitmqUri=null;
        
        try{
            rabbitmqUri = new URI(uri);
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
        
        CachingConnectionFactory ret = new CachingConnectionFactory(rabbitmqUri);
        logger.info(String.format("Created cachingConnectionFactory with hashCode %d" , ret.hashCode() ) );
        return ret;
    }
    
    @Bean(name="myQueue")
    Queue createMyQueue() {
        logger.info("Creating myQueue");
        return new Queue("myQueue", false);
    }

    @Bean(name="topicExchange")
    TopicExchange createTopicExchange() {
        logger.info("Creating topicExchange");
        return new TopicExchange("topicExchange");
    }

    @Bean(name="bindingQueue")
    Binding binding(@Qualifier("myQueue")Queue queue, @Qualifier("topicExchange")TopicExchange exchange) {
        logger.info("Creating bindingQueue");
        return BindingBuilder.bind(queue).to(exchange).with(queue.getName());
    }

    @Bean(name="workQueueContainer")
    SimpleMessageListenerContainer createWorkQueueContainer(ConnectionFactory connectionFactory, @Autowired @Qualifier("listenerAdapter") MessageListenerAdapter listenerAdapter,@Qualifier("myQueue") Queue queue) {
        logger.info("Creating createWorkQueueContainer for work queue");
        logger.info(String.format("Using ConnectionFactory with hashCode %d" , connectionFactory.hashCode() ) );
        
        SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
        smlc.setConnectionFactory(connectionFactory);
        smlc.setQueueNames(queue.getName() );
        smlc.setMessageListener(listenerAdapter);
        return smlc;
    }
    
    @Bean(name="listenerAdapter")    
    MessageListenerAdapter listenerAdapter(@Autowired @Qualifier("messageHandler")MessageHandler messageHandler) {
        logger.info("Creating listenerAdapter");
        return new MessageListenerAdapter(messageHandler);
    }

    @Bean(name="messageHandler")
    MessageHandler createMessageHandler() {
        logger.info("Creating messageHandler");
        return new MessageHandler();
    }
    
    //--------    
    @Bean(name="fanoutExchange")
    FanoutExchange createFanoutExchange() {
        logger.info("Creating fanoutExchange");
        return new FanoutExchange("fanoutExchange");
    }

    @Bean(name="anonymousQueue1")
    AnonymousQueue createAnonymousQueue1() {
        logger.info("Creating anonQueue1");
        return new AnonymousQueue();
    }

    @Bean(name="bindingTopic")
    Binding bindingTopic(@Qualifier("anonymousQueue1")AnonymousQueue queue, @Qualifier("fanoutExchange")FanoutExchange exchange) {
        logger.info("Creating bindingTopic");
        return BindingBuilder.bind(queue).to(exchange);
    }

    @Bean(name="broadcastMessageHandler")
    BroadcastMessageHandler createBroadcastMessageHandler() {
        logger.info("Creating broadcastMessageHandlermessageHandler");
        return new BroadcastMessageHandler();
    }
    
    @Bean(name="broadcastListenerAdapter")    
    MessageListenerAdapter createBroadcastlistenerAdapter(@Autowired @Qualifier("broadcastMessageHandler")BroadcastMessageHandler broadcastMessageHandler) {
        logger.info("Creating broadcastListenerAdapter");
        return new MessageListenerAdapter(broadcastMessageHandler);
    }

    @Bean(name="topicContainer")
    SimpleMessageListenerContainer createTopicContainer(ConnectionFactory connectionFactory, @Autowired @Qualifier("broadcastListenerAdapter") MessageListenerAdapter listenerAdapter, @Qualifier("anonymousQueue1")  AnonymousQueue anonQueue) {
        logger.info("Creating container for topic");
        logger.info(String.format("Using ConnectionFactory with hashCode %d" , connectionFactory.hashCode() ) );
        
        SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
        smlc.setConnectionFactory(connectionFactory);
        smlc.setQueueNames( anonQueue.getName() );
        smlc.setMessageListener(listenerAdapter);
        return smlc;
    }
    
    //----
//---- rabbit
}

}}}

persistence.xml
{{{
<persistence>
    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <jta-data-source>basicDataSource</jta-data-source>
        <properties>
            <!-- <property name="hibernate.hbm2ddl.auto" value="create-drop" /> -->
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.show_sql" value="false" />
        </properties>
    </persistence-unit>
</persistence>
}}}

greeting.html
{{{
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
}}}

test.sql
{{{
--JDBC URL: jdbc:mariadb://localhost:3306/springmvchtml
--mysql -u root -p
create database springmvchtml;
create user 'usertest'@'%' identified by '????';
create user 'usertest'@'localhost' identified by '????';
grant all on springmvchtml.* to 'usertest'@'%';
grant all on springmvchtml.* to 'usertest'@'localhost';
show grants for 'usertest'@'%';
show grants for 'usertest'@'localhost'; 
create table springmvchtml.dummy (name varchar(255) ) ;
insert into springmvchtml.dummy (name) values('aaaa');
insert into springmvchtml.dummy (name) values('bbbb');
commit;
-- mysql -u usertest -p
}}}

logback-spring.html
{{{
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
-->    
    <!-- override spring logback default behaviour -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
	<file>${filelog}</file>
	<encoder>
		<pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
	</encoder>
    </appender>

    <appender name="GREETFILE" class="ch.qos.logback.core.FileAppender">
        <file>/tmp/greet.log</file>
        <encoder>
                <pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
	<layout class="ch.qos.logback.classic.PatternLayout">
		<Pattern>%yellow(%date{ISO8601}) %green([%thread]) %highlight(%-5level) %cyan(%logger{35}) - %white(%msg%n) </Pattern>
	</layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE" />
        <appender-ref ref="CONSOLE"/>
    </root>

    <logger name="hello.GreetingController" level="debug" additivity="false">
        <appender-ref ref="GREETFILE"/>
	<appender-ref ref="CONSOLE" />
    </logger>

</configuration>
}}}

application.properties
{{{
logging.file=/tmp/testout.log
server.port=8443
foo.bar=bohica
instance.name=node1
keystore.file=springboot.p12
keystore.password=????
keystore.type=PKCS12
keystore.alias=springboot

db.driverclass=org.mariadb.jdbc.Driver
db.jdbcurl=jdbc:mariadb://localhost:3306/springmvchtml
db.username=usertest
db.password=????
db.initialsizeconnpool=3
#rabbimq parameters
rabbitmq.uri=amqp://????:????@localhost:5672
#redis 
spring.redis.host=localhost
spring.redis.password=????????
spring.redis.port=6379
}}}

pom.xml
{{{
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>hello</groupId>
    <artifactId>test-spring-boot</artifactId>
    <version>0.1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>1.4.4</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency> 
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <!-- http session redis -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>1.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>        
        <!-- http session redis -->
    </dependencies>
    <properties>
        <start-class>hello.Application</start-class>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
}}}

Libs
{{{
-rw-r--r--  1 vitor  vitor    37M Sep 29 15:49 test-spring-boot-0.1.0.jar
jar tf test-spring-boot-0.1.0.jar | grep BOOT | grep jar
BOOT-INF/lib/spring-session-1.2.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-data-redis-1.4.0.RELEASE.jar
BOOT-INF/lib/jackson-core-2.8.1.jar
BOOT-INF/lib/javassist-3.20.0-GA.jar
BOOT-INF/lib/jul-to-slf4j-1.7.21.jar
BOOT-INF/lib/groovy-2.4.7.jar
BOOT-INF/lib/spring-amqp-1.6.1.RELEASE.jar
BOOT-INF/lib/hibernate-validator-5.2.4.Final.jar
BOOT-INF/lib/classmate-1.3.1.jar
BOOT-INF/lib/hibernate-commons-annotations-5.0.1.Final.jar
BOOT-INF/lib/tomcat-embed-el-8.5.4.jar
BOOT-INF/lib/jedis-2.8.2.jar
BOOT-INF/lib/httpcore-4.4.5.jar
BOOT-INF/lib/antlr-2.7.7.jar
BOOT-INF/lib/xml-apis-1.4.01.jar
BOOT-INF/lib/spring-boot-starter-tomcat-1.4.0.RELEASE.jar
BOOT-INF/lib/thymeleaf-layout-dialect-1.4.0.jar
BOOT-INF/lib/spring-boot-autoconfigure-1.4.0.RELEASE.jar
BOOT-INF/lib/snakeyaml-1.17.jar
BOOT-INF/lib/http-client-1.0.0.RELEASE.jar
BOOT-INF/lib/httpclient-4.5.2.jar
BOOT-INF/lib/spring-boot-starter-logging-1.4.0.RELEASE.jar
BOOT-INF/lib/tomcat-jdbc-8.5.4.jar
BOOT-INF/lib/spring-beans-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-jdbc-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-jdbc-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-amqp-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-aspects-4.3.2.RELEASE.jar
BOOT-INF/lib/commons-dbcp-1.4.jar
BOOT-INF/lib/spring-data-commons-1.12.2.RELEASE.jar
BOOT-INF/lib/spring-context-support-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-data-keyvalue-1.1.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-1.4.0.RELEASE.jar
BOOT-INF/lib/tomcat-embed-websocket-8.5.4.jar
BOOT-INF/lib/commons-logging-1.2.jar
BOOT-INF/lib/spring-tx-4.3.2.RELEASE.jar
BOOT-INF/lib/validation-api-1.1.0.Final.jar
BOOT-INF/lib/spring-core-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-aop-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-data-jpa-1.10.2.RELEASE.jar
BOOT-INF/lib/commons-pool2-2.4.2.jar
BOOT-INF/lib/dom4j-1.6.1.jar
BOOT-INF/lib/tomcat-juli-8.5.4.jar
BOOT-INF/lib/jcl-over-slf4j-1.7.21.jar
BOOT-INF/lib/log4j-over-slf4j-1.7.21.jar
BOOT-INF/lib/aspectjweaver-1.8.9.jar
BOOT-INF/lib/spring-data-redis-1.7.2.RELEASE.jar
BOOT-INF/lib/spring-web-4.3.2.RELEASE.jar
BOOT-INF/lib/unbescape-1.1.0.RELEASE.jar
BOOT-INF/lib/mariadb-java-client-1.4.4.jar
BOOT-INF/lib/amqp-client-3.6.3.jar
BOOT-INF/lib/spring-oxm-4.3.2.RELEASE.jar
BOOT-INF/lib/tomcat-embed-core-8.5.4.jar
BOOT-INF/lib/spring-orm-4.3.2.RELEASE.jar
BOOT-INF/lib/hibernate-jpa-2.1-api-1.0.0.Final.jar
BOOT-INF/lib/commons-pool-1.6.jar
BOOT-INF/lib/thymeleaf-spring4-2.1.5.RELEASE.jar
BOOT-INF/lib/hibernate-core-5.0.9.Final.jar
BOOT-INF/lib/spring-context-4.3.2.RELEASE.jar
BOOT-INF/lib/thymeleaf-2.1.5.RELEASE.jar
BOOT-INF/lib/commons-codec-1.10.jar
BOOT-INF/lib/spring-boot-starter-web-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-webmvc-4.3.2.RELEASE.jar
BOOT-INF/lib/slf4j-api-1.7.21.jar
BOOT-INF/lib/javax.inject-1.jar
BOOT-INF/lib/jackson-annotations-2.8.1.jar
BOOT-INF/lib/jandex-2.0.0.Final.jar
BOOT-INF/lib/spring-messaging-4.3.2.RELEASE.jar
BOOT-INF/lib/ognl-3.0.8.jar
BOOT-INF/lib/hibernate-entitymanager-5.0.9.Final.jar
BOOT-INF/lib/spring-boot-starter-thymeleaf-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-retry-1.1.3.RELEASE.jar
BOOT-INF/lib/javax.transaction-api-1.2.jar
BOOT-INF/lib/logback-classic-1.1.7.jar
BOOT-INF/lib/spring-boot-starter-data-jpa-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-boot-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-expression-4.3.2.RELEASE.jar
BOOT-INF/lib/logback-core-1.1.7.jar
BOOT-INF/lib/jackson-databind-2.8.1.jar
BOOT-INF/lib/spring-rabbit-1.6.1.RELEASE.jar
BOOT-INF/lib/spring-aop-4.3.2.RELEASE.jar
BOOT-INF/lib/jboss-logging-3.3.0.Final.jar
}}}