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.
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.
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 Next Server 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
TRANSMISSION:
readTimeout: 480000
isWaitForFinishExecuteAllRequests: false
waitToShutdownThreadsOnStop: 60000
backlog: 0
receiveBufferSize: 0
isReuseAddress: false
threadPoolSize: 8
isLogPeriodicOnDebug: true
isDestroyThreadOnTimeout: false
threadExecutingTimeOut: 3600000
QUEUE:
readTimeout: 480000
isWaitForFinishExecuteAllRequests: true
waitToShutdownThreadsOnStop: 60000
backlog: 0
receiveBufferSize: 0
isReuseAddress: false
threadPoolSize: 128
isLogPeriodicOnDebug: true
isDestroyThreadOnTimeout: false
threadExecutingTimeOut: 240000
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'
switchDelayTime: 0
connectionSocketTimeoutInMillis: 1000
readTimeoutInMillis: 30000
expectedCheckResponseTimeInMillis: 2000
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
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 Next Server (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 Next Server Maven Plugin and also with our JCS JLupin Next Server IntelliJ Plugin.
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
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
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
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 Next Server 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
TRANSMISSION:
readTimeout: 480000
isWaitForFinishExecuteAllRequests: false
waitToShutdownThreadsOnStop: 60000
backlog: 0
receiveBufferSize: 0
isReuseAddress: false
threadPoolSize: 8
isLogPeriodicOnDebug: true
isDestroyThreadOnTimeout: false
threadExecutingTimeOut: 3600000
QUEUE:
readTimeout: 480000
isWaitForFinishExecuteAllRequests: true
waitToShutdownThreadsOnStop: 60000
backlog: 0
receiveBufferSize: 0
isReuseAddress: false
threadPoolSize: 128
isLogPeriodicOnDebug: true
isDestroyThreadOnTimeout: false
threadExecutingTimeOut: 240000
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'
switchDelayTime: 0
connectionSocketTimeoutInMillis: 1000
readTimeoutInMillis: 30000
expectedCheckResponseTimeInMillis: 2000
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
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 Next Server (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 Next Server Maven Plugin and also with our JCS JLupin Next Server 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 Next Server 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 org.springframework.stereotype.Service;
@Api
@Service(value = "calculatorService")
public class CalculatorServiceImpl implements CalculatorService {
@ApiOperation(value = "Add two numbers.")
@Override
public OperationOutput add(OperationInput operationInput) {
final OperationOutput operationOutput = new OperationOutput();
operationOutput.setResult(operationInput.getA() * operationInput.getB());
return operationOutput;
}
@ApiOperation(value = "Subtract two numbers.")
@Override
public OperationOutput sub(OperationInput operationInput) {
final OperationOutput operationOutput = new OperationOutput();
operationOutput.setResult(operationInput.getA() * operationInput.getB());
return operationOutput;
}
@ApiOperation(value = "Multiply two numbers.")
@Override
public OperationOutput mul(OperationInput operationInput) {
final OperationOutput operationOutput = new OperationOutput();
operationOutput.setResult(operationInput.getA() * operationInput.getB());
return operationOutput;
}
@ApiOperation(value = "Divide two numbers.")
@Override
public OperationOutput div(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.
<proejct>
[...]
<build>
[...]
<plugins>
[...]
<plugin>
<groupId>com.jlupin</groupId>
<artifactId>jlupin-next-server-maven-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>generate-swagger</goal>
</goals>
<configuration>
<apiSources>
<apiSource>
<locations>${project.groupId}</locations>
<info>
<title>Calculator API</title>
<version>${project.version}</version>
</info>
<swaggerDirectory>${build.directory}</swaggerDirectory>
</apiSource>
</apiSources>
</configuration>
</execution>
</executions>
</plugin>
[...]
</plugins>
[...]
</build>
[...]
</project>
Now generate Swagger API specification file with maven command mvn jlupin-next-server:generate-swagger --projects example/implementation
. You should see your swagger.json
inside example-implementation
target
directory. Next step is to serve this file through HTTP server wich CORS support enabled. You can do this with our static-files-provider
microservice. Just copy swagger.json
to static
direcotry inside microservice folder. Your file should be available at http://localhost:8000/jlupin-static-files-provider/swagger.json
. Then go to Swagger UI page http://petstore.swagger.io/ and paste your url in top of the page and clock Explore. Your api should be loaded and ready to play with.