Table of Contents


Introduction

This page contains useful information around the development of application services. Although, this information is not meant to be a fixed rule set for the development, but rather a helper to show how specific common problems may bo solved, it is desirable to use a common approach, for developing. The provided solution worked in at least one example.

Examples

The following examples are taken from the data-service-configuration project, which can be found here (develop branch @ the moment of writing): https://git.acc.gsi.de/sv-commons/data-service-configuration

Project Setup

The project setup is based on 3 artifacts, each with own POM and different packaging: - a jar with all the sources (e.g. data-service-configuration-core) the artifactid is data-service-configuration - a war assembly of the sources (e.g. data-service-configuratoin-war) - a stand alone service assembly of the sources to run the service (e.g. data-service-configuration-service)

This structure allows an easy build and deployment of both: a stand alone service based on spring boot application, as well as WAR file. All projects are combined in one git repository.

POM file of the core project:

<?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>

   <parent>
      <groupId>de.gsi.cs.co</groupId>
      <artifactId>csco-parent-java-service</artifactId>
      <version>13.0.0</version>
   </parent>

   <groupId>de.gsi.aco.sv</groupId>
   <artifactId>data-service-configuration</artifactId>
   <version>0.2.0-SNAPSHOT</version>

   <name>data-service-configuration</name>
   <packaging>jar</packaging>
   <description>A rest service to query the middleware configuration</description>

   <properties>
      <java.version>11</java.version>
      <oracle.connector.version>12.1.0.2</oracle.connector.version>

      <config.log4j.dev>log4j2-local.xml</config.log4j.dev>
      <config.log4j.int>log4j2-int.xml</config.log4j.int>
      <config.log4j.pro>log4j2-pro.xml</config.log4j.pro>
   </properties>

   <scm>
      <connection>scm:git:https://git.acc.gsi.de/sv-commons/${project.artifactId}.git/</connection>
      <developerConnection>scm:git:https://git.acc.gsi.de/sv-commons/${project.artifactId}.git</developerConnection>
      <url>https://git.acc.gsi.de/sv-commons/${project.artifactId}</url>
      <tag>HEAD</tag>
   </scm>

   <dependencies>

      <!-- Logging -->
      <dependency>
         <groupId>de.gsi.cs.co.ap.common</groupId>
         <artifactId>cscoap-common-dependencies</artifactId>
         <version>13.0.0</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jersey</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jdbc</artifactId>
         <exclusions>
            <exclusion>
               <groupId>org.apache.tomcat</groupId>
               <artifactId>tomcat-jdbc</artifactId>
            </exclusion>
            <exclusion>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
         </exclusions>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-log4j2</artifactId>
      </dependency>

      <!-- Needed for Async Logging with Log4j 2 -->
      <!-- TODO: Check if this is really needed for async logging -->
      <dependency>
         <groupId>com.lmax</groupId>
         <artifactId>disruptor</artifactId>
         <version>3.3.6</version>
      </dependency>

      <dependency>
         <groupId>com.oracle</groupId>
         <artifactId>ojdbc7</artifactId>
         <version>${oracle.connector.version}</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <inherited>false</inherited>
            <executions>
               <execution>
                  <id>zip files</id>
                  <phase>none</phase>
               </execution>
               <execution>
                  <id>zip csco-parent-java build files</id>
                  <phase>none</phase>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
</project>

The POM file of the service project:

<?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>

   <parent>
      <groupId>de.gsi.cs.co</groupId>
      <artifactId>csco-parent-java-service</artifactId>
      <version>13.0.0</version>
   </parent>
  
     <groupId>de.gsi.aco.sv</groupId>
     <artifactId>data-service-configuration-service</artifactId>
     <version>0.2.0-SNAPSHOT</version>
     <name>data-service-configuration</name>
  
     <properties>
      <bundle.mainClass>de.gsi.sc.co.sv.MwConfigurationApplication</bundle.mainClass>

      <config.log4j.dev>log4j2-local.xml</config.log4j.dev>
      <config.log4j.int>log4j2-int.xml</config.log4j.int>
      <config.log4j.pro>log4j2-pro.xml</config.log4j.pro>
   </properties>
  
     <dependencies>
        <dependency>
           <groupId>de.gsi.aco.sv</groupId>
         <artifactId>data-service-configuration</artifactId>
         <version>0.2.0-SNAPSHOT</version>
        </dependency>
     </dependencies>
  
     <dependencyManagement>
      <dependencies>
         <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
  
</project>

The POM file of the WAR project:
<?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>

   <parent>
      <groupId>de.gsi.cs.co</groupId>
      <artifactId>csco-parent-java</artifactId>
      <version>13.0.0</version>
   </parent>
  
     <groupId>de.gsi.aco.sv</groupId>
     <artifactId>data-service-configuration-war</artifactId>
     <version>0.2.0-SNAPSHOT</version>
     <packaging>war</packaging>
  
     <properties>
      <bundle.mainClass>de.gsi.sc.co.sv.MwConfigurationApplication</bundle.mainClass>
   </properties>
  
     <dependencies>
        <dependency>
           <groupId>de.gsi.aco.sv</groupId>
         <artifactId>data-service-configuration</artifactId>
         <version>0.2.0-SNAPSHOT</version>
        </dependency>
     </dependencies>
     
     <build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <inherited>false</inherited>
            <executions>
               <execution>
                  <id>zip files</id>
                  <phase>none</phase>
               </execution>
               <execution>
                  <id>zip csco-parent-java build files</id>
                  <phase>none</phase>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
  
     <dependencyManagement>
      <dependencies>
         <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
   
</project>
 

Note: The section in all POMs was required, to insure that the dependencies version used for specific assemblies are the same as in the core version (it seems as there is some internal issue with one of the projects).

Improvemets for the future

- The core component is still containing all the dependencies required for all assebly versions. I suppose that not all of those dependencies make sense. A better approach would be to let only those dependencies in the core project, which are really required there - Several application resources are not required in the core component, e.g. the database configuration should only be available inside of the standalone service package

Tomcat for deployments

A local tomcat application server, to test the deployment is available here: /common/usr/cscosv/opt/tomcat

To start the server simply call bin/startup.sh To stop the server bin/shutdown.sh

To deploy a war simply put a war file into the webapps folder of the server.

The Server is configured to run on 8090 port. If its running then you can to the following page using the browser: localhost:8090/. On this page you can click on "Server Status" button (Usr: tomcat, pass: tomcat) and then on "List Applications" to see the overview of all deployed application. If your application is listed, but the "Running" status is false, that it had some errors during the start. Those errors may be found in one of the logs in the ./log folder.

Additional information how to setup the Datasource for application can be found in the section "Database Connections"

Database Connections

The IT Infrastructure requirement foresees that the Datasource for the DB communications should be provided by the tomcat as a JNDI resource while a standalone application shall manage the connections by itself. The basic strategy to overcome with this requirement is to let the application known whether it is started locally or as a servlet on server. This knowledge is then used to define specific spring conditions providing the conditional configuration loading.

To do this first define a holder-class:

public class ConditionsContainer {

    private boolean startedByServletContainer = false;
    private boolean startedByMain = false;

    private static ConditionsContainer instance = null;

    private ConditionsContainer() {

    }

    public static ConditionsContainer instance() {
        if (instance == null) {
            instance = new ConditionsContainer();
        }
        return instance;
    }

    // Getters and Setters
    ...
}

Then specific conditions:

public class RunningLocallyCondition implements Condition {
    @Override
    public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        return ConditionsContainer.instance().isStartedLocally();
    }

and

public class RunningLocallyCondition implements Condition {
    @Override
    public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        return ConditionsContainer.instance().isStartedByServletContainer();
    }
}

Those conditions must be set at application start:

@SpringBootApplication
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MwConfigurationApplication extends SpringBootServletInitializer implements ServletContextListener {

    private static final Logger logger = LoggerFactory.getLogger(MwConfigurationApplication.class);

    @Override
    protected WebApplicationContext createRootApplicationContext(final ServletContext servletContext) {
   return super.createRootApplicationContext(servletContext);
    }
  
    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException {
      // see: https://stackoverflow.com/questions/33633098/java-lang-illegalstateexception-cannot-initialize-context-because-there-is-alre
      servletContext.setInitParameter("contextConfigLocation", "<NONE>");
      
                ConditionsContainer.instance().setStartedOnServer(true);
      super.onStartup(servletContext);
    }
    
    public void contextInitialized(ServletContextEvent sce) {}

    public void contextDestroyed(ServletContextEvent sce) {}
    
    public static void main(final String[] args) {
       logger.debug("Inside {}", MwConfigurationApplication.class);
        ConditionsContainer.instance().setStartedLocally(true);
        SpringApplication.run(MwConfigurationApplication.class);
    }
    
}

The configuration for the Datasource can be put into the application.properties file inside of the resources folder of the project. The content of the file can be like this:

   dirserver.db.url=jdbc:oracle:thin:@//dbla0ab.acc.gsi.de:1521/AccDbP.acc.gsi.de
   dirserver.db.username=<SCHEMA_OR_USER_NAME>
   dirserver.db.password=<PASSWORD>

Based on the conditions it is possible to define different datasources for different application start cases. One for the running the service in embedded container (stand-alone):

@Configuration
@Validated
//@PropertySource("classpath:/config/fesa.properties")
@ConfigurationProperties(prefix = "dirserver.db")
@Conditional(RunningLocallyCondition.class)
public class LocalDataSourceConfig {

   @Bean(name = "deviceConfigurationDao")
   @Autowired
   DeviceConfigurationDAO dcRepo(DataSource datasource) {
      DeviceConfigurationDAO pr = new DeviceConfigurationDAO();
      pr.setDataSource(datasource);
      return pr;
   }

   @NotNull
   private String username;

   @NotNull
   private String password;

   @NotNull
   private String url;

   public void setUsername(final String username) {
      this.username = username;
   }

   public void setPassword(final String password) {
      this.password = password;
   }

   public void setUrl(final String url) {
      this.url = url;
   }

   @Bean
   @Primary
   JdbcTemplate jdbcTemplate() throws SQLException {
      return new JdbcTemplate(fesaDbDataSource());
   }

   @Bean(destroyMethod = "")
   @Primary
   DataSource fesaDbDataSource() throws SQLException {
      final OracleDataSource dataSource = new OracleDataSource();
      dataSource.setUser(username);
      dataSource.setPassword(password);
      dataSource.setURL(url);
      dataSource.setLoginTimeout(20);
      dataSource.setImplicitCachingEnabled(true);
      dataSource.setFastConnectionFailoverEnabled(true);
      return dataSource;
   }
}

Notes:

- The Prefix defined in the ConfigurationProperties annotation must correspond to the naming in the properties file - All Spring beans, which need the Database connection should be created by the configuration class (only one bean in this case)

In addition the configuration for the Server deployment:

@Configuration
@Conditional(RunningOnServerCondition.class)
public class ServerDataSourceConfig {
    
   @Bean(name = "deviceConfigurationDao")
   @Autowired
   DeviceConfigurationDAO dcRepo(DataSource datasource) {
      DeviceConfigurationDAO pr = new DeviceConfigurationDAO();
      pr.setDataSource(datasource);
      return pr;
   }
    
    @Bean(destroyMethod = "")
    @Primary
    DataSource dataSource() throws NamingException {
        final JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
        //bean.setJndiName("java:comp/env/jdbc/fesa");
        bean.setJndiName("java:comp/env/jdbc/dirserver");
        bean.setProxyInterface(DataSource.class);
        bean.setLookupOnStartup(false);
        bean.afterPropertiesSet();
        return (DataSource) bean.getObject();
    }
}

Note: The actual name of the JNDI resource in this case is "jdbc/dirserver" while the "java:comp/env" is a root resource prefix. This prefix may not be required, but it did not worked without it during my tests. Also, to use the auto wiring of Spring the bean names should be same, as they used by the according components:
@Component
@Path("")
public class DeviceConfigurationController {

    private static final Logger logger = LoggerFactory.getLogger(DeviceConfigurationController.class);

    @Autowired
    DeviceConfigurationDAO deviceConfigurationDao;

    @GET
    @Path("/device/{nomenclature}")
    @Produces({ MediaType.APPLICATION_JSON })
    public DeviceConfiguration getDeviceConfiguration(@PathParam("nomenclature") final String device) {
        final DeviceConfiguration dc = deviceConfigurationDao.findByName(device);
        if (dc != null) {
            logger.debug("Got the DeviceConfiguration: {} for device {}", device);
        } else {
            logger.debug("No Configuration found for device {}", device);
        }
        return dc;
    }

}

Configuration of the Datasource in tomcat

The configuration of the tomcat datasource is defined in the conf/context.xml file in a similar way the Infrastucture Team does this. In the example above following should be added to the file:

<Resource name="jdbc/dirserver"
   global="jdbc/dirserver"
   auth="Container"
       type="javax.sql.DataSource"
       driverClassName="oracle.jdbc.driver.OracleDriver"
       username={User_or_schema_name}
       password={Password}
       url="jdbc:oracle:thin:@//dbla0ab.acc.gsi.de:1521/AccDbP{or U, T etc).acc.gsi.de"
   maxTotal="2"
   maxIdle="30" />

Logging

The logging as a stand-alone application is quite simple and follows the general rules for all application at GSI. To use the logging following dependency should be added to the pom file:

                <!-- Logging -->
      <dependency>
         <groupId>de.gsi.cs.co.ap.common</groupId>
         <artifactId>cscoap-common-dependencies</artifactId>
         <version>13.0.0</version>
      </dependency>

The acutal logging configuration can be set in dependece to the environment using the properties section in the pom file:

       <properties>
      <config.log4j.dev>log4j2-local.xml</config.log4j.dev>
      <config.log4j.int>log4j2-int.xml</config.log4j.int>
      <config.log4j.pro>log4j2-pro.xml</config.log4j.pro>
   </properties>

The standard configuraiton (dev, int, pro) is aviable over the dependecy above. Own configuration must be exposed over the classpath.

Probably the easeast and most convinient way to setup the logging configuration for the servlet is to put the log4j2.xml (or .properties) file into the servlets classpath. The logging to the graylog may be configurated as follows:

<Configuration status="warn" name="cscoap" packages="de.gsi.cs.co.ap.common.dependencies.log4j2">

    <Properties>
        <Property name="messagePattern">%5p [%d{dd MMM yyyy HH:mm:ss,SSS}] (%F) - %m%n</Property>
        <!-- 
        set program to system property (-Dapp.name)
        if this is not set fall back to servletContextName (set within a war file)
        if this is not set use the contextPath (URL of the servlet)
        if this is not set use the string unknown
        -->
        <Property name="program">${sys:app.name:-${web:servletContextName:-${web:contextPath:-unknown}}}</Property>
    </Properties>


    <Appenders>
        <Gelf name="LogstashGelf" host="udp:graylog.acc.gsi.de" port="12201" version="1.1" extractStackTrace="true"
            ignoreExceptions="false" originHost="%host{fqdn}" includeFullMdc="true" filterStackTrace="true" >
            <Field name="timestamp" pattern="%d{dd MMM yyyy HH:mm:ss,SSS}" />
            <Field name="level" pattern="%level" />
            <Field name="logger" pattern="%logger" />
            <Field name="thread" pattern="%t" />
            <Field name="pid" pattern="%pid" />
            <Field name="program" pattern="data-service-configuration" />
            <Field name="user_name" pattern="${sys:user.name}" />
            <Field name="java_version" pattern="${sys:java.version}" />
        </Gelf>

        <Async name="AsyncSocket">
            <AppenderRef ref="LogstashGelf" />
        </Async>
        
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${messagePattern}" />
        </Console>
        
    </Appenders>

    <Loggers>
        <Root level="warn">
            <AppenderRef ref="AsyncSocket" />
        </Root>
    </Loggers>

</Configuration>

Caching

There is no correct and tested approach the the moment, but the new directory server uses the Infinispan, so it may worth a try to use as well and write a small report here
Topic revision: r8 - 27 Sep 2019, BenjaminPeter
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback