Basics

HTTP Elastic API is an automated text communication entry point. It is used to call remotely enabled services. By default it is exposed by Main Server on port 8082.

Figure 1. Exposed HTTP Elastic API entry point for native microservice.

HTTP Elastic API is only exposed for native (nativeApp) microservices. For servlet (servletApp) microservices it is not possible to expose it as microservices are handling all incoming http requests by themselves.

ROA type Elastic API is analogue to common communication pattern known widely as REST.


Example application

For all examples in this chapter will be used example application which consists of files listed below:

Input object class:

package com.example.service.pojo;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("mulInput")
public class MulInput {
    private Integer a;
    private Integer b;

    public void setA(Integer a) {
        this.a = a;
    }

    public Integer getA() {
        return a;
    }

    public void setB(Integer b) {
        this.b = b;
    }

    public Integer getB() {
        return b;
    }
}

Output object class:

package com.example.service.pojo;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("mulOutput")
public class MulOutput {
    private Integer result;

    public void setResult(Integer result) {
        this.result = result;
    }

    public Integer getResult() {
        return result;
    }
}

Service interface:

package com.example.service.interfaces;

import com.example.service.pojo.MulInput;
import com.example.service.pojo.MulOutput;

public interface MultiplierService {
    Integer mulOne(Integer a, Integer b);
    MulOutput mulTwo(MulInput mulInput);
    Integer mulThree(MulInput mulInput);
    MulOutput mulFour(Integer a, Integer b);
}

Service implementation:

package com.example.service.impl;

import com.example.service.interfaces.MultiplierService;
import com.example.service.pojo.MulInput;
import com.example.service.pojo.MulOutput;
import org.springframework.stereotype.Service;

@Service(value = "multiplierService")
public class MultiplierServiceImpl implements MultiplierService {
    @Override
    public Integer mulOne(Integer a, Integer b) {
        return a * b;
    }

    @Override
    public MulOutput mulTwo(MulInput mulInput) {
        final MulOutput mulOutput = new MulOutput();
        mulOutput.setResult(mulInput.getA() * mulInput.getB());
        return mulOutput;
    }

    @Override
    public Integer mulThree(MulInput mulInput) {
        return mulInput.getA() * mulInput.getB();
    }

    @Override
    public MulOutput mulFour(Integer a, Integer b) {
        final MulOutput mulOutput = new MulOutput();
        mulOutput.setResult(a * b);
        return mulOutput;
    }
}

Spring configuration file:

package com.example.configuration;

import com.example.service.pojo.MulInput;
import com.example.service.pojo.MulOutput;
import com.jlupin.impl.client.util.JLupinClientUtil;
import com.jlupin.interfaces.client.delegator.JLupinDelegator;
import com.jlupin.interfaces.common.enums.PortType;
import com.thoughtworks.xstream.XStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;

@Configuration
@ComponentScan("com.example")
public class ExampleSpringConfiguration {

    @Bean
    public JLupinDelegator getJLupinDelegator() {
        return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC);
    }


    @Bean(name = "jLupinRegularExpressionToRemotelyEnabled")
    public List getRemotelyBeanList() {
        List<String> list = new ArrayList<>();
        list.add("multiplierService");
        // list.add("<REMOTE_SERVICE_NAME>");
        return list;
    }

    @Bean(name = "xStreamXmlSerializer")
    public XStream getXStreamXmlSerializer() {
        XStream xStream = new XStream();
        xStream.processAnnotations(MulInput.class);
        xStream.processAnnotations(MulOutput.class);
        return xStream;
    }
}

JLupin Platform configuration file:

package com.example.configuration;

import com.jlupin.impl.container.application.spring.JLupinAbstractSpringApplicationContainer;
import com.jlupin.interfaces.configuration.microservice.container.application.JLupinAbstractApplicationContainerProducer;
import com.jlupin.interfaces.container.application.JLupinApplicationContainer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

public class ExampleJLupinConfiguration extends JLupinAbstractApplicationContainerProducer {
    @Override
    public JLupinApplicationContainer produceJLupinApplicationContainer() {
        return new JLupinAbstractSpringApplicationContainer() {
            @Override
            public AbstractApplicationContext getAbstractApplicationContext() {
                return new AnnotationConfigApplicationContext(ExampleSpringConfiguration.class);
            }
        };
    }
}

Compile all above classes and pack them into jar archive. Then create example directory somewhere. This will be the name of the microservice. Put inside newly created jar archive and microservice configuration file. Example configuration is shown below. If you want to know more about configuration microservices please follow here.

Microservice configuration.yml file:

SERVERS:
  JLRMC: #JLupin Remote Method Calls Fast Protocol:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: true
    waitToShutdownThreadsOnStop: 60000
    backlog: 0
    receiveBufferSize: 0
    isReuseAddress: false
    threadPoolSize: 128
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 240000
    isStartOnMainServerInitialize: true
  TRANSMISSION:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: false
    waitToShutdownThreadsOnStop: 60000
    backlog: 0
    receiveBufferSize: 0
    isReuseAddress: false
    threadPoolSize: 8
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 3600000
    isStartOnMainServerInitialize: true
  QUEUE:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: true
    waitToShutdownThreadsOnStop: 60000
    backlog: 1024
    receiveBufferSize: 1024
    isReuseAddress: false
    threadPoolSize: 128
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 240000
    isStartOnMainServerInitialize: true
ENTRY_POINTS:
  QUEUE:
    threadAmount: 512
    howOftenCheckingServerInMillis: 5000
    repeatsAmount: 4
    timeToWaitBetweenRepeatProbeInMillis: 1000
TRANSMISSION:
  MICROSERVICES_GRANT_ACCESS:
    MICROSERVICES_LIST:
      #- microserviceName: 'sampleMicroservice'
      #  serviceName: 'sampleServiceName'
      #  methodName: 'sampleMethodName'
      #- microserviceName: 'sampleMicroservice2'
      #  serviceName: 'sampleServiceName2'
      #  methodName: 'sampleMethodName2'
PROPERTIES:
  #jvmOptions1: '-Xms128M -Xmx256M -agentlib:jdwp=transport=dt_socket,address=12998,server=y,suspend=n'
  jvmOptions1: '-Xms64M -Xmx128M' #jvmOptions_2 - default the same as jvmOptions_1
  #jvmOptions2: '-Xms128M -Xmx256M'
  externalPort: '8000'
  version: '1.5.0.0'
  switchDelayTime: 0
  connectionSocketTimeoutInMillis: 1000
  readTimeoutInMillis: 30000
  isKeepAlive: false
  isOOBInline: false
  isTcpNoDelay: false
  isReuseAddress: false
  sendBufferSize: 0
  receiveBufferSize: 0
  soLinger: 0
  trafficClass: 0
  #javaExecutablePath: 'c:\\jvm\\bin\\java.exe'
  #additionalClassPath: 'c:\\temp\\*'
  isStartOnMainServerInitialize: true
  priorityStartOnMainServerInitialize: 4
  waitForProcessInitResponseTimeInMillis: 90000
  waitForProcessStartResponseTimeInMillis: 90000
  waitForProcessDestroyResponseTimeInMillis: 30000
  isAllFilesToJVMAppClassLoader: false
  isArchiveOnStart: false
  startLogMode: INFO
  isInitErrorCauseWithNetworkInformation: true
  isJmxEnabled: true
  jmxOptions: '-Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false'
  jmxPrimaryPort: -1
  jmxSecondaryPort: -1
  checkAvailableScript: 'function isAvailable(checkResponseTimeInMillis, jrmcActiveThreads, jrmcMaxThreads,
                                              queueActiveThreads, queueMaxThreads, servletActiveThreads, servletMaxThreads,
                                              jvmMaxMemoryInBytes, jvmTotalMemoryInBytes, jvmFreeMemoryInBytes,
                                              jvmProcessCpuLoadInPercentage, userAvailableFlag) {
                          var isAvailableByUser = Boolean(userAvailableFlag);
                            if(checkResponseTimeInMillis > 20000 || !isAvailableByUser) {
                               return false;
                            }
                            return true;
                         }'
APPLICATION:
  applicationContainerProducerClassName: 'com.example.configuration.ExampleJLupinConfiguration'
INITIALIZING_LOGGER:
  #directoryPath: '/logs/server'
  #fileName: 'file_name'
  fileExtension: 'log'
  fileSizeInMB: 20
  maxFiles: 10
MEMORY_ERRORS:
  isRestartOnError: true
  howManyTimes: 4
  percentageGrowth: 15
  isHeapDump: true
THREAD_POOLS:
  THREAD_POOL_1:
    size: 8
    waitingTimeForTasksCompletionInMillis: 10000
  #THREAD_POOL_2:
  #  size: 8
  #  waitingTimeForTasksCompletionInMillis: 10000

You can also include here configuration file for Log4j2 (log4j2.xml):

<?xml version="1.0" encoding="UTF-8"?>

<!-- ===================================================================== -->
<!--                                                                       -->
<!--  Log4j2 Configuration                                                  -->
<!--                                                                       -->
<!-- ===================================================================== -->

<!--
   | For more configuration information and examples see the Apache Log4j2
   | website: https://logging.apache.org/log4j/2.x/index.html
-->

<Configuration status="WARN">
    <!-- Extract log directory and file name into variables -->
    <Properties>
        <Property name="logDirectory">../logs/microservice/example</Property>
        <Property name="logFileName">microservice</Property>
    </Properties>

    <Appenders>
        <!-- RollingFileAppender configured to role every day -->
        <RollingFile name="FILE">
            <FileName>${logDirectory}/${logFileName}.log</FileName>
            <FilePattern>${logDirectory}/${logFileName}.%d{yyyy-MM-dd}.log</FilePattern>

            <!-- Compress log files to gzip -->
            <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy -->
            <!-- <FilePattern>${logDirectory}/${logFileName}.%d{yyyy-MM-dd}.log.gz</FilePattern> -->

            <!-- Do not truncate file -->
            <Append>true</Append>

            <!-- The default pattern: Date Priority [Category] (Thread) Message\n -->
            <PatternLayout pattern="%d %-5p [%c] (%t) %m%n" />

            <Policies>
                <!-- Rollover every microservice start - very useful for debugging -->
                <!-- <OnStartupTriggeringPolicy /> -->

                <!-- Rollover at the top of each day -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />

                <!-- Rollover if file size is greater than 200 MB -->
                <!-- <SizeBasedTriggeringPolicy size="200 MB"/> -->
            </Policies>

            <!-- Keep last 10 log files -->
            <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy -->
            <!-- <DefaultRolloverStrategy max="10" /> -->
        </RollingFile>

        <!-- AsyncAppender for high performance -->
        <Async name="ASYNC_FILE">
            <BufferSize>1000</BufferSize>
            <AppenderRef ref="FILE" />
        </Async>
    </Appenders>

    <Loggers>
        <!-- Setup for root logger with AsyncAppender -->
        <Root level="info">
            <AppenderRef ref="ASYNC_FILE" />
        </Root>
    </Loggers>
</Configuration>

Now pack your example directory into zip file example.zip (inside zip at top level there should be visible example directory), start JLupin Platform (start.sh/start.cmd) and run control (control.sh/control.cmd). Copy created zip file example.zip into upload directory. Then from control window run command microservice upload example.zip. Your microservice will be uploaded to server and started. You can significantly speed up this process with our Continous Delivery Maven Plugin and also with our JLupin Platform Development Tool.


Using Elastic API

For Elastic API all additional parameters of invocation had been moved to request headers. Request body contains only serialized data sent to method. These way it is easy to configure your client to send proper requests. You cannot overload method names when using ElasticAPI. There are two main options to invoke service methods:

  • Remote Object API (ROA) - requires called method to have only one parameter
  • Remote Method Call (RMC) - no limits.

Headers specification

Header Description Example value
X-JLNS-API-ID API ID to tell server which facade to use. ROA, RMC (case sensitive)
X-JLNS-Privilege-Names List of privileged names separated by semicolon used with JLupinPermissionResolver to check if call is privileged. privileged1;privileged2
X-JLNS-Locale Locale for translation. en
X-JLNS-Sequence-Name Sequence name to use for deserializing method parameters. xStreamParamArrayXmlInOutSequence, xStreamParamArrayJsonInOutSequence, jacksonParamArrayXmlInOutSequence, jacksonParamArrayJsonInOutSequence
X-JLNS-Request-ID Request ID. request1
X-JLNS-Session-ID Session ID. session1
X-JLNS-User User. user1
X-JLNS-Client-Application-Name Client application name. application1
X-JLNS-IP IP address. 127.0.0.1

All above parameters are optional. By default API ID and sequence name is taken from main server configuration file:

[...]
ENTRY_POINTS:
    WEB_SERVICE_FACADE_HTTP:
        [...]
        defaultSequenceName: jacksonParamArrayJsonInOutSequence
        defaultApiId: ROA
        [...]
[...]

URL structure:

/[microserviceName]/[serviceName]/[methodName]

All requests must be invoked with method POST (also support for options is added for web browsers). Content-Type or Accept must be send to determine serialization type. Also both headers must be consistent (you can’t send JSON data and as return get XML data).

Body specification

ROA

For ROA request body is a serialized method argument. As said before through ROA only methods with one argument can be called. For example from MultiplierService only mulTwo and mulThree methods can be called. As an input they accept MulInput object.

Example for JSON (when jacksonParamArrayJsonInOutSequence is used):

{
    "a": 3,
    "b": 4
}

Example for XML (when xStreamParamArrayXmlInOutSequence is used):

<mulInput>
    <a>3</a>
    <b>4</b>
</mulInput>

RMC

For RMC request body is an array, where objects are serialized arguments. As said before through RMC you can call all your methods (remember that method names overloading is forbidden). For example from MultiplierService all methods could be called.

Example for JSON (when jacksonParamArrayJsonInOutSequence is used) for methods mulTwo and mulThree:

[
    {
        "a": 3,
        "b": 4
    }
]

Example for JSON (when jacksonParamArrayJsonInOutSequence is used) for methods mulOne and mulFour:

[
    3,
    4
]

Example for XML (when xStreamParamArrayXmlInOutSequence is used) for methods mulTwo and mulThree:

<paramArray>
    <mulInput>
        <a>3</a>
        <b>4</b>
    </mulInput>
</paramArray>

Example for XML (when xStreamParamArrayXmlInOutSequence is used) for methods mulOne and mulFour:

<paramArray>
    <int>3</int>
    <int>4</int>
</paramArray>


Example calls

Below are showed full examples of requests including request headers, body and response.

ROA

JSON

mulTwo

Request:

POST /example/multiplierService/mulTwo HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
Content-Length: 16

{"a": 3, "b": 4}

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulTwo" -H "Accept: application/json" -H "Content-Type: application/json" -d '{"a": 3, "b": 4}'

Response:

{"result":12}

mulThree

Request:

POST /example/multiplierService/mulThree HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
Content-Length: 16

{"a": 3, "b": 4}

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulThree" -H "Accept: application/json" -H "Content-Type: application/json" -d '{"a": 3, "b": 4}'

Response:

12

XML

mulTwo

Request:

POST /example/multiplierService/mulTwo HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 37

<mulInput><a>3</a><b>4</b></mulInput>

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulTwo" -H "Accept: application/xml" -H "Content-Type: application/xml" -H "X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence" -d '<mulInput><a>3</a><b>4</b></mulInput>'

Response:

<mulOutput>
  <result>12</result>
</mulOutput>

mulThree

Request:

POST /example/multiplierService/mulThree HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 37

<mulInput><a>3</a><b>4</b></mulInput>

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulThree" -H "Accept: application/xml" -H "Content-Type: application/xml" -H "X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence" -d '<mulInput><a>3</a><b>4</b></mulInput>'

Response:

<int>12</int>

RMC

JSON

mulOne

Request:

POST /example/multiplierService/mulOne HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
X-JLNS-API-ID: RMC
Content-Length: 6

[3, 4]

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulOne" -H "Accept: application/json" -H "Content-Type: application/json" -H "X-JLNS-API-ID: RMC" -d '[3, 4]'

Response:

12

mulTwo

Request:

POST /example/multiplierService/mulTwo HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
X-JLNS-API-ID: RMC
Content-Length: 18

[{"a": 3, "b": 4}]

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulTwo" -H "Accept: application/json" -H "Content-Type: application/json" -H "X-JLNS-API-ID: RMC" -d '[{"a": 3, "b": 4}]'

Response:

{"result":12}

mulThree

Request:

POST /example/multiplierService/mulThree HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
X-JLNS-API-ID: RMC
Content-Length: 18

[{"a": 3, "b": 4}]

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulThree" -H "Accept: application/json" -H "Content-Type: application/json" -H "X-JLNS-API-ID: RMC" -d '[{"a": 3, "b": 4}]'

Response:

12

mulFour

Request:

POST /example/multiplierService/mulFour HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
X-JLNS-API-ID: RMC
Content-Length: 6

[3, 4]

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulFour" -H "Accept: application/json" -H "Content-Type: application/json" -H "X-JLNS-API-ID: RMC" -d '[3, 4]'

Response:

{"result":12}

XML

mulOne

Request:

POST /example/multiplierService/mulOne HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-API-ID: RMC
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 49

<paramArray><int>3</int><int>4</int></paramArray>

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulOne" -H "Accept: application/xml" -H "Content-Type: application/xml" -H "X-JLNS-API-ID: RMC" -H "X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence" -d '<paramArray><int>3</int><int>4</int></paramArray>'

Response:

<int>12</int>

mulTwo

Request:

POST /example/multiplierService/mulTwo HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-API-ID: RMC
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 62

<paramArray><mulInput><a>3</a><b>4</b></mulInput></paramArray>

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulTwo" -H "Accept: application/xml" -H "Content-Type: application/xml" -H "X-JLNS-API-ID: RMC" -H "X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence" -d '<paramArray><mulInput><a>3</a><b>4</b></mulInput></paramArray>'

Response:

<mulOutput>
  <result>12</result>
</mulOutput>

mulThree

Request:

POST /example/multiplierService/mulThree HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-API-ID: RMC
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 62

<paramArray><mulInput><a>3</a><b>4</b></mulInput></paramArray>

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulThree" -H "Accept: application/xml" -H "Content-Type: application/xml" -H "X-JLNS-API-ID: RMC" -H "X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence" -d '<paramArray><mulInput><a>3</a><b>4</b></mulInput></paramArray>'

Response:

<int>12</int>

mulFour

Request:

POST /example/multiplierService/mulFour HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-API-ID: RMC
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 49

<paramArray><int>3</int><int>4</int></paramArray>

Curl command:

curl -X POST "http://localhost:8082/example/multiplierService/mulFour" -H "Accept: application/xml" -H "Content-Type: application/xml" -H "X-JLNS-API-ID: RMC" -H "X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence" -d '<paramArray><int>3</int><int>4</int></paramArray>'

Response:

<mulOutput>
  <result>12</result>
</mulOutput>


Architecture

Below architecture of Elastic API is shown with detailed where deserialization is taking place.

ROA

Figure 2. HTTP Elastic API ROA serialization/deserialization.

Your request is send to Main Server (1). It takes it and extracts body into String variable. Then through JLRMC (binary communication) your serialized object as a String is send to your microservice. Where deserialization is taking place (2). Appropirate serializer/deserializer is used based on choosen sequence (can be set with appropriate header in request). When object is deserialized, method is called (3) and as an argument deserialized object is passed.

Example with JSON

Request:

POST /example/multiplierService/mulTwo HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
Content-Length: 16

{"a": 3, "b": 4}

Response:

{"result":12}

Main Server handles HTTP request and changes it to JLRMC (binary communication). Microservice receives String "{\"a\": 3, \"b\": 4}" which is deserialized by jackson json serializer/deserializer (because default sequence is jacksonParamArrayJsonInOutSequence). Then method mulTwo is executed with argument being MulInput{a=3, b=4}. The result MulOutput{result=12} is serialized by jackson json serializer/deserializer (because default sequence is jacksonParamArrayJsonInOutSequence). Then serialized result is send back to Main Server which returns it.

Example with XML

Request:

POST /example/multiplierService/mulTwo HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 37

<mulInput><a>3</a><b>4</b></mulInput>

Response:

<mulOutput>
  <result>12</result>
</mulOutput>

Main Server handles HTTP request and changes it to JLRMC (binary communication). Microservice receives String "<mulInput><a>3</a><b>4</b></mulInput>" which is deserialized by xstream xml serializer/deserializer (because used sequence is xStreamParamArrayXmlInOutSequence). Then method mulTwo is executed with argument being MulInput{a=3, b=4}. The result MulOutput{result=12} is serialized by xstream xml serializer/deserializer (because used sequence is xStreamParamArrayXmlInOutSequence). Then serialized result is send back to Main Server which returns it.

RMC

Figure 3. HTTP Elastic API RMC serialization/deserialization.

Your request is send to Main Server (1). It takes it and extracts body and devides it into seperate String objects. Each array argument becomes one String. Then through JLRMC (binary communication) your serialized objects as a Strings are send to your microservice. Where deserialization is taking place (2). Appropirate serializer/deserializer is used based on choosen sequence (can be set with appropriate header in request). When objects are deserialized, method is called (3) and as an arguments deserialized objects are passed.

Example with JSON

Request:

POST /example/multiplierService/mulFour HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/json
Content-Type: application/json
X-JLNS-API-ID: RMC
Content-Length: 6

[3, 4]

Response:

{"result":12}

Main Server handles HTTP request and changes it to JLRMC (binary communication). Microservice receives array with two Strings: "3", "4". They are deserialized (each of them seperately) by jackson json serializer/deserializer (because default sequence is jacksonParamArrayJsonInOutSequence). Then method mulFour is executed with arguments being Integer{3}, Integer{4}. The result MulOutput{result=12} is serialized by jackson json serializer/deserializer (because default sequence is jacksonParamArrayJsonInOutSequence). Then serialized result is send back to Main Server which returns it.

Example with XML

Request:

POST /example/multiplierService/mulFour HTTP/1.1
Host: localhost:8082
User-Agent: curl/7.52.1
Accept: application/xml
Content-Type: application/xml
X-JLNS-API-ID: RMC
X-JLNS-Sequence-Name: xStreamParamArrayXmlInOutSequence
Content-Length: 49

<paramArray><int>3</int><int>4</int></paramArray>

Response:

<mulOutput>
  <result>12</result>
</mulOutput>

Main Server handles HTTP request and changes it to JLRMC (binary communication). Microservice receives array with two Strings: "<int>3</int>", "<int>4</int>". They are deserialized (each of them seperately) by xstream xml serializer/deserializer (because used sequence is xStreamParamArrayXmlInOutSequence). Then method mulFour is executed with arguments being Integer{3}, Integer{4}. The result MulOutput{result=12} is serialized by xstream xml serializer/deserializer (because used sequence is xStreamParamArrayXmlInOutSequence). Then serialized result is send back to Main Server which returns it.


Configuring serializer/deserializer

xStreamParamArrayXmlInOutSequence

Sequence xStreamParamArrayXmlInOutSequence is looking in your application context for a bean named xStreamXmlSerializer which type is XStream. If you do not define it by yourself JLupin will provide default definition which is:

@Bean(name = "xStreamXmlSerializer")
public XStream getXStreamXmlSerializer() {
    return new XStream();
}

But if you define it by yourself you are free to cofigure it as you want. JLupin will always use this bean for serialization/deserialization when sequence xStreamParamArrayXmlInOutSequence is used.

xStreamParamArrayJsonInOutSequence

Sequence xStreamParamArrayJsonInOutSequence is looking in your application context for a bean named xStreamJsonSerializer which type is XStream. If you do not define it by yourself JLupin will provide default definition which is:

@Bean(name = "xStreamJsonSerializer")
public XStream getXStreamJsonSerializer() {
    return new XStream(new JettisonMappedXmlDriver());
}

But if you define it by yourself you are free to cofigure it as you want. JLupin will always use this bean for serialization/deserialization when sequence xStreamParamArrayJsonInOutSequence is used.

jacksonParamArrayXmlInOutSequence

Sequence jacksonParamArrayXmlInOutSequence is looking in your application context for a bean named jacksonXmlSerializer which type is XmlMapper. If you do not define it by yourself JLupin will provide default definition which is:

@Bean(name = "jacksonXmlSerializer")
public XmlMapper getJacksonXmlSerializer() {
    return new XmlMapper();
}

But if you define it by yourself you are free to cofigure it as you want. JLupin will always use this bean for serialization/deserialization when sequence jacksonParamArrayXmlInOutSequence is used.

jacksonParamArrayJsonInOutSequence

Sequence jacksonParamArrayJsonInOutSequence is looking in your application context for a bean named jacksonJsonSerializer which type is ObjectMapper. If you do not define it by yourself JLupin will provide default definition which is:

@Bean(name = "jacksonJsonSerializer")
public ObjectMapper getJacksonJsonSerializer() {
  return new ObjectMapper();
}

But if you define it by yourself you are free to cofigure it as you want. JLupin will always use this bean for serialization/deserialization when sequence jacksonParamArrayJsonInOutSequence is used.


Swagger integration

ROA type Elastic API is analogue to common communication pattern known widely as REST. RMC type is using array as input which is not supported by Swagger (only single object can be an input for endpoint).

If you are using ROA you can integrate with swagger to generate documentation and client libraries. The example will use JSON message format with standard Jackson serializer. Below are shown project files:

Input object class:

package com.example.service.pojo;

public class OperationInput {
    private Integer a;
    private Integer b;

    public void setA(Integer a) {
        this.a = a;
    }

    public Integer getA() {
        return a;
    }

    public void setB(Integer b) {
        this.b = b;
    }

    public Integer getB() {
        return b;
    }
}

Output object class:

package com.example.service.pojo;

public class OperationOutput {
    private Integer result;

    public void setResult(Integer result) {
        this.result = result;
    }

    public Integer getResult() {
        return result;
    }
}

Service interface:

package com.example.service.interfaces;

import com.example.service.pojo.OperationInput;
import com.example.service.pojo.OperationOutput;

public interface CalculatorService {
    OperationOutput add(OperationInput operationInput);
    OperationOutput sub(OperationInput operationInput);
    OperationOutput mul(OperationInput operationInput);
    OperationOutput div(OperationInput operationInput);
}

Service implementation:

package com.example.service.impl;

import com.example.service.interfaces.CalculatorService;
import com.example.service.pojo.OperationInput;
import com.example.service.pojo.OperationOutput;
import org.springframework.stereotype.Service;

@Service(value = "calculaotrService")
public class CalculatorServiceImpl implements CalculatorService {
    @Override
    public OperationOutput add(OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }

    @Override
    public OperationOutput sub(OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }

    @Override
    public OperationOutput mul(OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }

    @Override
    public OperationOutput div(OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }
}

Spring configuration file:

package com.example.configuration;

import com.example.service.pojo.MulInput;
import com.example.service.pojo.MulOutput;
import com.jlupin.impl.client.util.JLupinClientUtil;
import com.jlupin.interfaces.client.delegator.JLupinDelegator;
import com.jlupin.interfaces.common.enums.PortType;
import com.thoughtworks.xstream.XStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;

@Configuration
@ComponentScan("com.example")
public class ExampleSpringConfiguration {
    @Bean
    public JLupinDelegator getJLupinDelegator() {
        return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC);
    }
    @Bean(name = "jLupinRegularExpressionToRemotelyEnabled")
    public List getRemotelyBeanList() {
        List<String> list = new ArrayList<>();
        list.add("calculatorService");
        // list.add("<REMOTE_SERVICE_NAME>");
        return list;
    }
}

JLupin Platform configuration file:

package com.example.configuration;

import com.jlupin.impl.container.application.spring.JLupinAbstractSpringApplicationContainer;
import com.jlupin.interfaces.configuration.microservice.container.application.JLupinAbstractApplicationContainerProducer;
import com.jlupin.interfaces.container.application.JLupinApplicationContainer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

public class ExampleJLupinConfiguration extends JLupinAbstractApplicationContainerProducer {
    @Override
    public JLupinApplicationContainer produceJLupinApplicationContainer() {
        return new JLupinAbstractSpringApplicationContainer() {
            @Override
            public AbstractApplicationContext getAbstractApplicationContext() {
                return new AnnotationConfigApplicationContext(ExampleSpringConfiguration.class);
            }
        };
    }
}

Compile all above classes and pack them into jar archive. Then create example directory somewhere. This will be the name of the microservice. Put inside newly created jar archive and microservice configuration file. Example configuration is shown below. If you want to know more about configuration microservices please follow here.

Microservice configuration.yml file:

SERVERS:
  JLRMC: #JLupin Remote Method Calls Fast Protocol:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: true
    waitToShutdownThreadsOnStop: 60000
    backlog: 0
    receiveBufferSize: 0
    isReuseAddress: false
    threadPoolSize: 128
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 240000
    isStartOnMainServerInitialize: true
  TRANSMISSION:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: false
    waitToShutdownThreadsOnStop: 60000
    backlog: 0
    receiveBufferSize: 0
    isReuseAddress: false
    threadPoolSize: 8
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 3600000
    isStartOnMainServerInitialize: true
  QUEUE:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: true
    waitToShutdownThreadsOnStop: 60000
    backlog: 1024
    receiveBufferSize: 1024
    isReuseAddress: false
    threadPoolSize: 128
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 240000
    isStartOnMainServerInitialize: true
ENTRY_POINTS:
  QUEUE:
    threadAmount: 512
    howOftenCheckingServerInMillis: 5000
    repeatsAmount: 4
    timeToWaitBetweenRepeatProbeInMillis: 1000
TRANSMISSION:
  MICROSERVICES_GRANT_ACCESS:
    MICROSERVICES_LIST:
      #- microserviceName: 'sampleMicroservice'
      #  serviceName: 'sampleServiceName'
      #  methodName: 'sampleMethodName'
      #- microserviceName: 'sampleMicroservice2'
      #  serviceName: 'sampleServiceName2'
      #  methodName: 'sampleMethodName2'
PROPERTIES:
  #jvmOptions1: '-Xms128M -Xmx256M -agentlib:jdwp=transport=dt_socket,address=12998,server=y,suspend=n'
  jvmOptions1: '-Xms64M -Xmx128M' #jvmOptions_2 - default the same as jvmOptions_1
  #jvmOptions2: '-Xms128M -Xmx256M'
  externalPort: '8000'
  version: '1.5.0.0'
  switchDelayTime: 0
  connectionSocketTimeoutInMillis: 1000
  readTimeoutInMillis: 30000
  isKeepAlive: false
  isOOBInline: false
  isTcpNoDelay: false
  isReuseAddress: false
  sendBufferSize: 0
  receiveBufferSize: 0
  soLinger: 0
  trafficClass: 0
  #javaExecutablePath: 'c:\\jvm\\bin\\java.exe'
  #additionalClassPath: 'c:\\temp\\*'
  isStartOnMainServerInitialize: true
  priorityStartOnMainServerInitialize: 4
  waitForProcessInitResponseTimeInMillis: 90000
  waitForProcessStartResponseTimeInMillis: 90000
  waitForProcessDestroyResponseTimeInMillis: 30000
  isAllFilesToJVMAppClassLoader: false
  isArchiveOnStart: false
  startLogMode: INFO
  isInitErrorCauseWithNetworkInformation: true
  isJmxEnabled: true
  jmxOptions: '-Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false'
  jmxPrimaryPort: -1
  jmxSecondaryPort: -1
  checkAvailableScript: 'function isAvailable(checkResponseTimeInMillis, jrmcActiveThreads, jrmcMaxThreads,
                                              queueActiveThreads, queueMaxThreads, servletActiveThreads, servletMaxThreads,
                                              jvmMaxMemoryInBytes, jvmTotalMemoryInBytes, jvmFreeMemoryInBytes,
                                              jvmProcessCpuLoadInPercentage, userAvailableFlag) {
                          var isAvailableByUser = Boolean(userAvailableFlag);
                            if(checkResponseTimeInMillis > 20000 || !isAvailableByUser) {
                               return false;
                            }
                            return true;
                         }'
APPLICATION:
  applicationContainerProducerClassName: 'com.example.configuration.ExampleJLupinConfiguration'
INITIALIZING_LOGGER:
  #directoryPath: '/logs/server'
  #fileName: 'file_name'
  fileExtension: 'log'
  fileSizeInMB: 20
  maxFiles: 10
MEMORY_ERRORS:
  isRestartOnError: true
  howManyTimes: 4
  percentageGrowth: 15
  isHeapDump: true
THREAD_POOLS:
  THREAD_POOL_1:
    size: 8
    waitingTimeForTasksCompletionInMillis: 10000
  #THREAD_POOL_2:
  #  size: 8
  #  waitingTimeForTasksCompletionInMillis: 10000

You can also include here configuration file for Log4j (log4j2.xml):

<?xml version="1.0" encoding="UTF-8"?>

<!-- ===================================================================== -->
<!--                                                                       -->
<!--  Log4j2 Configuration                                                  -->
<!--                                                                       -->
<!-- ===================================================================== -->

<!--
   | For more configuration information and examples see the Apache Log4j2
   | website: https://logging.apache.org/log4j/2.x/index.html
-->

<Configuration status="WARN">
    <!-- Extract log directory and file name into variables -->
    <Properties>
        <Property name="logDirectory">../logs/microservice/example</Property>
        <Property name="logFileName">microservice</Property>
    </Properties>

    <Appenders>
        <!-- RollingFileAppender configured to role every day -->
        <RollingFile name="FILE">
            <FileName>${logDirectory}/${logFileName}.log</FileName>
            <FilePattern>${logDirectory}/${logFileName}.%d{yyyy-MM-dd}.log</FilePattern>

            <!-- Compress log files to gzip -->
            <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy -->
            <!-- <FilePattern>${logDirectory}/${logFileName}.%d{yyyy-MM-dd}.log.gz</FilePattern> -->

            <!-- Do not truncate file -->
            <Append>true</Append>

            <!-- The default pattern: Date Priority [Category] (Thread) Message\n -->
            <PatternLayout pattern="%d %-5p [%c] (%t) %m%n" />

            <Policies>
                <!-- Rollover every microservice start - very useful for debugging -->
                <!-- <OnStartupTriggeringPolicy /> -->

                <!-- Rollover at the top of each day -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />

                <!-- Rollover if file size is greater than 200 MB -->
                <!-- <SizeBasedTriggeringPolicy size="200 MB"/> -->
            </Policies>

            <!-- Keep last 10 log files -->
            <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy -->
            <!-- <DefaultRolloverStrategy max="10" /> -->
        </RollingFile>

        <!-- AsyncAppender for high performance -->
        <Async name="ASYNC_FILE">
            <BufferSize>1000</BufferSize>
            <AppenderRef ref="FILE" />
        </Async>
    </Appenders>

    <Loggers>
        <!-- Setup for root logger with AsyncAppender -->
        <Root level="info">
            <AppenderRef ref="ASYNC_FILE" />
        </Root>
    </Loggers>
</Configuration>

Now pack your example directory into zip file example.zip (inside zip at top level there should be visible example directory), start JLupin Platform (start.sh/start.cmd) and run control (control.sh/control.cmd). Copy created zip file example.zip into upload directory. Then from control window run command microservice upload example.zip. Your microservice will be uploaded to server and started. You can significantly speed up this process with our JCS JLupin Platform Maven Plugin and also with our JCS JLupin Platform IntelliJ Plugin.

So now we have prepared application with ROA api available. We want to create Swagger API specification file which then will be used by all Swagger tools. First of all we need to add annotations to our service implementation to tell which elements belong to API. We will use JCS Jlupin Platform Maven Plugin with goal generate-swagger.

Let's start with modifying CalculatorServiceImpl class:

package com.example.application.service.impl;

import com.example.application.service.interfaces.CalculatorService;
import com.example.application.service.pojo.OperationInput;
import com.example.application.service.pojo.OperationOutput;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.stereotype.Service;

@Api
@Service(value = "calculatorService")
public class CalculatorServiceImpl implements CalculatorService {
    @ApiOperation(value = "Add two numbers.")
    @Override
    public OperationOutput add(@ApiParam("Operation input.") OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }

    @ApiOperation(value = "Subtract two numbers.")
    @Override
    public OperationOutput sub(@ApiParam("Operation input.") OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }

    @ApiOperation(value = "Multiply two numbers.")
    @Override
    public OperationOutput mul(@ApiParam("Operation input.") OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }

    @ApiOperation(value = "Divide two numbers.")
    @Override
    public OperationOutput div(@ApiParam("Operation input.") OperationInput operationInput) {
        final OperationOutput operationOutput = new OperationOutput();
        operationOutput.setResult(operationInput.getA() * operationInput.getB());
        return operationOutput;
    }
}

@Api annotation tells that class is included into external API. @ApiOperation tells which methods are included.

Add configuration for generating Swagger API specification to pom.xml file of example microservice. Base path set below contains microservice name example. Base path pattern is /_eapi/ELASTIC_HTTP/[microservice_name].

<proejct>
    [...]
    <build>
        [...]
        <plugins>
            [...]
            <plugin>
                <groupId>com.jlupin</groupId>
                <artifactId>jlupin-platform-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <goal>generate-swagger</goal>
                        </goals>
                        <configuration>
                            <apiSources>
                                <apiSource>
                                    <locations>
                                        <location>${project.groupId}</location>
                                    </locations>
                                    <info>
                                        <title>Calculator API</title>
                                        <version>${project.version}</version>
                                    </info>
                                    <swaggerDirectory>${project.build.directory}</swaggerDirectory>
                                    <basePath>/_eapi/ELASTIC_HTTP/example</basePath>
                                </apiSource>
                            </apiSources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            [...]
        </plugins>
        [...]
    </build>
    [...]
</project>

Now generate Swagger API specification file with maven command mvn jlupin-platform:generate-swagger --projects example/implementation. Run it form yuour mian project directory. Option --projects example/implementation selects only one microservice form whole project to run. If your microserivce has different name or project has different structure - just remember to set it properly. You should see your swagger.json inside example-implementation target directory. Now copy this file into your microservice directory on platform (platform/application/example/). Then enable addons on port 8000 on edge balancer by adding line include services/addons.conf; to edge balancer configuration file for 8000 port (platform/start/configuration/edge_servers/edge8000.conf) and restart nginx. Now you can open Swagger UI server by platform edge balancer. Just go to page http://localhost:8000/_swagger/example (example here is a name of microservice, url pattern is http://localhost:8000/_swagger/[microservice_name]). Your api should be loaded and ready to play with.

Figure 3. HTTP Elastic API with Swagger UI.

To execute operation just click selected one (green background) which will expand the box. Then on the right upper corner of green box click Try it out blue button. Now fields become editable. Provided data you want to send and scroll down looking for blue button Execute (it should be in the end of operation description). Click it and you will see result of operation just below.