Tuesday 14 January 2020

Enabling https in DXA Spring Boot Application

In my previous article, I presented the steps for converting a Java DXA webapp to a Spring Boot application. I would now like to present the steps for enabling https in this context.

Spring Boot's documentation specifies a few simple configuration additions to the application.properties file to enable https. However, in my experience these were insufficient to make the DXA Spring Boot application connect to the Tridion Sites microservices over https.

The alternative working approach involved extending the embedded Tomcat application server by setting some properties against the Connector and Http11NioProtocol.

The following bespoke properties were added to the dxa.properties file.

# Tomcat connector configuration
server.tomcat.port=8080
server.tomcat.ssl.enabled=true
server.tomcat.ssl.key-alias=ssl-key-alias.com
server.tomcat.ssl.key-store=certificates/key-store-file.jks
server.tomcat.ssl.key-password=SslKeyPassw0rd!
server.tomcat.ssl.key-store-type=JKS
server.tomcat.ssl.trust-store=certificates/trustStore.jks
server.tomcat.ssl.trust-store-password=SslTrustStorePassw0rd!
server.tomcat.ssl.certificate-fs-store-location=DXA/certificates

The certificate files are placed within the /src/main/resources/certificates folder in the application. This means these files will be embedded in the built WAR file inside the /certificates folder.

The certificate files cannot be referenced using the absolute path when deployed by Spring Boot inside an embedded Tomcat application server, so these need to be written to the file system onto the location specified by the server.tomcat.ssl.certificate-fs-store-location property.

IMPORTANT NOTE: if using Maven with a <resource> configuration entry specifying <filtering>true</filtering>, this will cause the certificate files (.jks in this example) to become corrupted.

The bespoke properties are referenced in the Spring Boot application using the @Value annotation.

@Value("${server.tomcat.port}")
private Integer port;

@Value("${server.tomcat.ssl.enabled:false}")
private boolean enableSsl;

@Value("${server.tomcat.ssl.key-alias:}")
private String sslKeyAlias;

@Value("${server.tomcat.ssl.key-store:}")
private String sslKeyStore;

@Value("${server.tomcat.ssl.key-password:}")
private String sslKeyPassword;

@Value("${server.tomcat.ssl.key-store-type:}")
private String sslKeyStoreType;

@Value("${server.tomcat.ssl.trust-store:}")
private String sslTrustStore;

@Value("${server.tomcat.ssl.trust-store-password:}")
private String sslTrustStorePassword;

@Value("${server.tomcat.ssl.certificate-fs-store-location:}")
private String certificateFileSystemStoreLocation;

An org.springframework.core.io.ResourceLoader is autowired to support reading the .jks certificate files from the configured location (i.e. certificates/key-store-file.jks).

The customization is implemented by invoking the addConnectorCustomizers method against the TomcatEmbeddedServletContainerFactory as shown below.

@Bean
public EmbeddedServletContainerCustomizer addConnectorCustomizers(){
  return container -> {
    if(container instanceof TomcatEmbeddedServletContainerFactory){
      TomcatEmbeddedServletContainerFactory factory =
        (TomcatEmbeddedServletContainerFactory)container;
      factory.addConnectorCustomizers(connector -> {
        connector.setPort(port);
        if (enableSsl) {
          Http11NioProtocol protocol = 
            (Http11NioProtocol)connector.getProtocolHandler();
          connector.setScheme("https");
          connector.setSecure(true);
          protocol.setSSLEnabled(true);
          try {
            FileSystemResource keyStoreFile = new 
              FileSystemResource(getResource(sslKeyStore,
                sslKeyPassword));
            FileSystemResource trustStoreFile = new 
              FileSystemResource(getResource(sslTrustStore, 
                sslTrustStorePassword));

            System.setProperty("javax.net.ssl.trustStore", 
              trustStoreFile.getFile().getAbsolutePath());
            System.setProperty("javax.net.ssl.trustStorePassword",
              sslTrustStorePassword);

            protocol.setKeyAlias(sslKeyAlias);
            protocol.setKeyPass(sslKeyPassword);

            protocol.setKeystoreFile(keyStoreFile.getFile()
              .getAbsolutePath());
            protocol.setKeystorePass(sslKeyPassword);

            protocol.setTruststoreFile(trustStoreFile.getFile()
              .getAbsolutePath());

            protocol.setTruststorePass(sslTrustStorePassword);
          }
          catch (Exception exception) {
            throw new RuntimeException(
              "Error setting up the SSL configuration [" +
                exception.getMessage() + "]");
          }
        }
      });
    }
  };
}

Resources


For completeness, the supporting code and property extracts can be found here.

Tuesday 7 January 2020

Converting Java DXA Webapp to Spring Boot APP

In this article, I would like to present the steps for converting the Java DXA (v2.2.1) application into a Spring Boot application.

Executable Artifact


As stated in Spring Boot’s documentation (https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-jsp-limitations), when running a Spring Boot application that uses an embedded servlet container (and is packaged as an executable archive), there are some limitations in the JSP support. The recommended approach is to build the solution as an executable WAR file, which can be launched with java -jar just like a normal jar executable.

Spring Boot dependencies


I have utilized an alternative to the starter spring-boot-starter-parent project since many implementations have a custom Maven parent. It is still possible to benefit from the spring-boot-starter-parent project's dependency tree by adding a dependency spring-boot-dependencies into our project in import scope.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>1.5.21.RELEASE</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>

The chosen Spring Boot version (1.5.21.RELEASE) automatically provides all the Spring dependencies in the version (v4.3.24.RELEASE) required by the DXA 2.2.1 framework. This way no other Spring Framework dependencies are needed in the Maven pom file.

The other required Spring Boot dependencies are:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>1.5.21.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>1.5.21.RELEASE</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
  <version>8.5.40</version>
  <scope>provided</scope>
</dependency>

The spring-boot-starter-tomcat and tomcat-embed-jasper dependencies are added to enable running the built executable WAR file inside an embedded Tomcat application server.

The spring-boot-maven-plugin is utilized to repackage the standard WAR build into an executable WAR file.

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>1.5.21.RELEASE</version>
  <configuration>
    <executable>true</executable>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
      <configuration>
        <classifier>spring-boot</classifier>
        <mainClass>com.sdl.webapp.main.WebAppInitializer</mainClass>
      </configuration>
    </execution>
  </executions>
</plugin>

A full pom example can be viewed here.


Initializing the Application


The WebAppInitializer class is the starting point of the Spring Boot application.

package com.sdl.webapp.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebAppInitializer {

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


This class is annotated with @SpringBootApplication, which among other things, tells Spring to look for other components, configurations and services inside the same package. Therefore, I placed the SpringInitializer class inside the same package annotated with @Import(DxaSpringInitialization.class) in order to kick off the DXA initialization process.

package com.sdl.webapp.main;

import com.sdl.dxa.DxaSpringInitialization;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Import(DxaSpringInitialization.class)
@Configuration
public class SpringInitializer {
}



Application Properties


The spring.profiles.active property had to be moved from the dxa.properties file into Spring Boot's application.properties file.

Here are the contents of the application.properties file.

logging.config=classpath:logback.xml
spring.profiles.active=cil.providers.active,search.solr



Deploying the Application


The built executable WAR file is deployed from the command-line using:

java -jar dxaSpringBoot-2.2.1-spring-boot.war

The DXA application is started inside an embedded Tomcat application server.


Resources


For completeness, the supporting code and property extracts can be found here.


Tuesday 16 April 2019

Complete a Task Step in SDL WorldServer using the REST API

In this article, I would like to present a method for completing an active Task Step in SDL WorldServer using the REST API.

The method takes three parameters:
  1. wsBaseUrl
  2. token
  3. completeTaskStepRequestBody
The wsBaseUrl must be the <serverURL>:<portnumber> where your WorldServer instance is running.

The second parameter is a security token, which can be retrieved by using the SDL WorldServer REST API as explained here.

The third parameter is a CompleteTaskStepRequestBody object containing the data to support the task step completion. This includes the id (task id), the transitionId and an optional comment to set as metadata. The task id and transition id can be retrieved by invoking the REST call described here for retrieving the tasks from a specified Project. The returned TaskDetails object provides the data.

The method returns a boolean value specifying whether the task step completion was successful.

Download an asset associated with a TASK in SDL WorldServer using the REST API

In this article, I would like to present a method for downloading a file (asset) associated with a Task in SDL WorldServer using the REST API.

The method takes six parameters:
  1. wsBaseUrl
  2. token
  3. taskId
  4. assetName
  5. downloadLocation
  6. assetLocationType
The wsBaseUrl must be the <serverURL>:<portnumber> where your WorldServer instance is running.

The second parameter is a security token, which can be retrieved by using the SDL WorldServer REST API as explained here.

The third parameter is the id of the Task in SDL WorldServer to download the asset from.

The fourth parameter is the name of the asset to download. This can be retrieved by invoking the REST call described here for retrieving the tasks from a specified Project. The returned TaskDetails object provides the targetAsset attribute containing the path to the target asset. Use this path to work out the file (asset) name.

The firth parameter is the path to the download location where to place the downloaded file.

Finally the sixth parameter is used to determine the location type: SOURCE, TARGET or SOURCE_TARGET.

The method returns a String containing the path to the downloaded file.

Get a list of Tasks from a Project in SDL WorldServer using the REST API

In this article, I would like to present a method for retrieving a list of Tasks from a Project in SDL WorldServer using the REST API.

The method takes three parameters:
  1. wsBaseUrl
  2. token
  3. projectId
The wsBaseUrl must be the <serverURL>:<portnumber> where your WorldServer instance is running.

The second parameter is a security token, which can be retrieved by using the SDL WorldServer REST API as explained here.

The third parameter is the id of the Project in SDL WorldServer to retrieve the tasks from.

The method returns a List of TaskDetails objects, one for each task returned, containing a subset of the JSON response data.

Create Project Groups in SDL WorldServer using the REST API

In this article, I would like to present a method for creating a Project Group in SDL WorldServer using the REST API.

The method takes three parameters:
  1. wsBaseUrl
  2. token
  3. projectGroups
The wsBaseUrl must be the <serverURL>:<portnumber> where your WorldServer instance is running.

The second parameter is a security token, which can be retrieved by using the SDL WorldServer REST API as explained here.

The third parameter is a List of ProjectGroup objects, each one containing the data required for creating each Project Group in WorldServer. This includes the namedescriptionprojectTypeIdclientId, the list of files for translation (systemFiles) and the list of Locales (target locales). The systemFiles attribute (List of String) must be populated with each asset's internalName returned by the file upload process described here.

The method returns a List of CreateProjectGroupResponse objects, one for each project group created, containing the JSON response data. With this data, it is possible to verify if the call was successful and also retrieve the newly created project group id.

Upload files to SDL WorldServer using the REST API

In this article, I would like to present a method for uploading a collection of files to SDL WorldServer using the REST API.

The method takes three parameters:
  1. wsBaseUrl
  2. token
  3. files
The wsBaseUrl must be the <serverURL>:<portnumber> where your WorldServer instance is running.

The second parameter is a security token, which can be retrieved by using the SDL WorldServer REST API as explained here.

The third parameter is a Collection of File objects for upload.

The method returns a List of FileUploadResponse objects, one for each uploaded file, containing data from the uploaded asset. With this information, it is possible to create Projects in WorldServer using the REST API and place these assets through translation.