Configure Spring Session with Redis

build.gradle

compile('org.springframework.session:spring-session')
compile('org.springframework.boot:spring-boot-starter-data-redis')

application.properties

spring.session.store-type=redis

spring.redis.host=localhost
#spring.redis.password=secret
spring.redis.port=6379

Receive JSON data in Spring Boot MVC Controller

FormsApiController.java

@RequestMapping(value = "/api/form/saveForm", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Map saveForm(HttpServletRequest request, HttpServletResponse response, @RequestBody String payload) {
        SessionManager sessionManager = new SessionManager(request, response);

        Map result = null;

        FormBL formBL = new FormBL(request, response);
        result = formBL.saveForm(payload);

        return result;
    }

FormBL.java

    public Map<String, Object> saveForm(String payload) {
        Map<String, Object> result = new HashMap<>();

        try {

            Gson gson = new Gson();
            PojoForm pojoForm = gson.fromJson(payload, PojoForm.class);

            Form form = new Form();
            if (pojoForm.formId > 0) {
                form.id = pojoForm.formId;
            }


            Machinery machinery = null;

            if (pojoForm.machineryId > 0) {
                machinery = Statics.machineryRepository.findById(pojoForm.machineryId);
            }

            form.name = pojoForm.formName;
            form.machinery = machinery;

            Form dbForm = Statics.formRepository.save(form);
            pojoForm.formId = dbForm.id;

            if (pojoForm.formRows.size() > 0) {
                for (PojoFormRow row : pojoForm.formRows) {
                    FormRow formRow = new FormRow();

                    if (row.getId() > 0) {
                        formRow.id = row.getId();
                    }

                    formRow.rowOrder = row.row_order;
                    formRow.rowType = row.getRowType();
                    formRow.title = row.getTitle();
                    formRow.form = dbForm;

                    FormRow dbFormRow = Statics.formRowRepository.save(formRow);
                    row.id = dbFormRow.id;

                    if (row.getRow_values().size() > 0) {
                        for (PojoRowValue rowValue : row.getRow_values()) {
                            RowSelectValue rowSelectValue = new RowSelectValue();

                            if (rowValue.getId() > 0) {
                                rowSelectValue.id = rowValue.getId();
                            }

                            rowSelectValue.title = rowValue.title;
                            rowSelectValue.titleOrder = rowValue.title_order;
                            rowSelectValue.formRow = dbFormRow;

                            RowSelectValue dbRowSelectValue = Statics.rowSelectValueRepository.save(rowSelectValue);
                            rowValue.id = dbRowSelectValue.id;
                        }
                    }
                }
            }

            result.put("form", "");
            result.put("error", 0);
        } catch (Exception ex) {
            // exception
            result.put("error", 1);
            ex.printStackTrace();
        }

        return result;
    }

How to Configure Spring Session with JDBC for MySQL

build.gradle

buildscript {
	ext {
		springBootVersion = '1.5.2.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'org.springframework.boot'

jar {
	baseName = 'logbook-server'
	version = '0.0.1-SNAPSHOT'
}

sourceCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-data-jpa')
	compile('org.springframework.session:spring-session')
	compile('org.springframework.boot:spring-boot-starter-thymeleaf')
	compile('org.springframework.boot:spring-boot-starter-web')
	compile('org.springframework.boot:spring-boot-starter-web-services')
	compile('org.springframework.boot:spring-boot-starter-websocket')
	runtime('org.springframework.boot:spring-boot-devtools')
	runtime('mysql:mysql-connector-java')
	compileOnly('org.springframework.boot:spring-boot-configuration-processor')
	compileOnly('org.projectlombok:lombok')
	testCompile('org.springframework.boot:spring-boot-starter-test')

	// https://mvnrepository.com/artifact/joda-time/joda-time
	compile group: 'joda-time', name: 'joda-time', version: '2.9.7'

	// https://mvnrepository.com/artifact/net.time4j/time4j-parent
	compile group: 'net.time4j', name: 'time4j-parent', version: '4.25'

	// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
	compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5'

	// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
	compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.56'

	// https://mvnrepository.com/artifact/com.google.code.gson/gson
	compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'

	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-tomcat
	compile group: 'org.springframework.boot', name: 'spring-boot-starter-tomcat'

	// https://mvnrepository.com/artifact/commons-io/commons-io
	compile group: 'commons-io', name: 'commons-io', version: '2.5'
}

application.properties

server.port=13602
spring.session.store-type=jdbc
server.compression.enabled=true
server.use-forward-headers=true
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,text/css,application/javascript
logging.file=logbook-server.log
spring.datasource.url=jdbc:mysql://localhost/logbook
spring.datasource.username=root
spring.datasource.password=12345
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.tomcat.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=50

# Validate the connection before borrowing it from the pool.
spring.datasource.tomcat.test-on-borrow=true

spring.jpa.hibernate.ddl-auto=create

HttpSessionConfig.java

@Configuration
@EnableJdbcHttpSession
public class HttpSessionConfig {

}

then execute this script on MySQL

CREATE TABLE SPRING_SESSION (
        SESSION_ID CHAR(36),
        CREATION_TIME BIGINT NOT NULL,
        LAST_ACCESS_TIME BIGINT NOT NULL,
        MAX_INACTIVE_INTERVAL INT NOT NULL,
        PRINCIPAL_NAME VARCHAR(100),
        CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
) ENGINE=InnoDB;

CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
        SESSION_ID CHAR(36),
        ATTRIBUTE_NAME VARCHAR(200),
        ATTRIBUTE_BYTES BLOB,
        CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
        CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
) ENGINE=InnoDB;

CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

References
http://docs.spring.io/spring-session/docs/current/reference/html5/#api-jdbcoperationssessionrepository-storage
http://stackoverflow.com/questions/37398905/spring-session-with-jdbc-configuration-table-test-spring-session-doesnt-exis

Use Spring Boot behind Apache front-end proxy server

Apache

a2enmod proxy
a2enmod ssl
a2enmod proxy_http
a2enmod proxy_ajp
a2enmod rewrite
a2enmod deflate
a2enmod headers
a2enmod proxy_balancer
a2enmod proxy_connect
a2enmod proxy_html
sudo a2enmod remoteip
sudo service apache2 restart
<VirtualHost *:80>
  ServerName iterator.ir

  ProxyRequests Off
  ProxyPreserveHost On
  RemoteIPHeader X-Forwarded-For
  <Proxy *>
    Order deny,allow
    Allow from all
  </Proxy>

  ProxyPass / http://localhost:13602/
  ProxyPassReverse / http://localhost:13602/
</VirtualHost>
<VirtualHost *:443>
	SSLEngine on
	RewriteEngine on
	SSLCertificateKeyFile /etc/letsencrypt/live/lastlab.pupli.net/privkey.pem
	SSLCertificateFile /etc/letsencrypt/live/lastlab.pupli.net/cert.pem
	SSLCertificateChainFile /etc/letsencrypt/live/lastlab.pupli.net/chain.pem
	ServerName lastlab.pupli.net
 
	ProxyRequests Off
	ProxyPreserveHost On
	RemoteIPHeader X-Forwarded-For
	<Proxy *>
		Order deny,allow
		Allow from all
	</Proxy>
 
	ProxyPass / http://localhost:14001/
	ProxyPassReverse / http://localhost:14001/
</VirtualHost>

Spring Boot
set server.use-forward-headers to server.use-forward-headers in Spring Boot application.properties

Java

String ipAddress = request.getHeader("X-FORWARDED-FOR");
if (ipAddress == null) {
	   ipAddress = request.getRemoteAddr();
}

References
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html#howto-use-tomcat-behind-a-proxy-server
https://www.mkyong.com/java/how-to-get-client-ip-address-in-java/
http://serverfault.com/questions/130925/passing-ip-address-with-mod-proxy
https://www.leaseweb.com/labs/2014/12/tutorial-apache-2-4-transparent-reverse-proxy/
http://www.thegeekstuff.com/2011/07/Apache-Virtual-Host/
https://devops.profitbricks.com/tutorials/configure-apache-as-a-reverse-proxy-using-mod_proxy-on-ubuntu/
https://www.digitalocean.com/community/tutorials/how-to-use-apache-http-server-as-reverse-proxy-using-mod_proxy-extension

Spring Data MongoDB – Indexes, Annotations and Converters

Indexed
This annotation marks the field as indexed in MongoDB

@QueryEntity
@Document
public class User {
    @Indexed
    private String name;
     
    ... 
}

Compound Indexes
MongoDB supports compound indexes, where a single index structure holds references to multiple fields.

@QueryEntity
@Document
@CompoundIndexes({
    @CompoundIndex(name = "email_age", def = "{'email.id' : 1, 'age': 1}")
})
public class User {
    //
}

Transient
As you would expect, this simple annotation excludes the field from being persisted in the database.

public class User {
     
    @Transient
    private Integer yearOfBirth;
    // standard getter and setter
 
}

Field
@Field indicates the key to be used for the field in the JSON document

@Field("email")
private EmailAddress emailAddress;

References
http://www.baeldung.com/spring-data-mongodb-index-annotations-converter