JavaScript

JavaScript is not compiled into by code but there are available JavaScript engines which runs on JVM. Example of such a engine is Nashorn (it is available by default since Java 8).

Native microservice

You can use JavaScript engine to execute service definition which will be written in JavaScript. But it is neccessary to include both languages: Java and JavaScript.

Project structure and dependencies

This is standard structure of Java project with Maven. The structure should look like this:

Figure 1. Structure of project.

Remember to configure Maven by editing pm.xml file:

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

    <groupId>com.example</groupId>
    <artifactId>javascript-hello-world</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <jlupin.version>1.5.0.0</jlupin.version>
        <jlupin.next.server.maven.plugin.version>1.2.1</jlupin.next.server.maven.plugin.version>

        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <repositories>
        <!-- Repository is also accessible using https connection: -->
        <!-- https://support.jlupin.com/maven2/ -->
        <repository>
            <id>jlupin-central</id>
            <name>jlupin</name>
            <url>http://support.jlupin.com/maven2/</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <!-- Repository is also accessible using https connection: -->
        <!-- https://support.jlupin.com/maven2/ -->
        <pluginRepository>
            <id>jlupin-central</id>
            <name>jlupin</name>
            <url>http://support.jlupin.com/maven2/</url>
        </pluginRepository>
    </pluginRepositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.9.RELEASE</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.jlupin</groupId>
            <artifactId>jlupin-client-assembly</artifactId>
            <version>${jlupin.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/js</directory>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>com.jlupin</groupId>
                <artifactId>jlupin-platform-maven-plugin</artifactId>
                <version>${jlupin.next.server.maven.plugin.version}</version>
                <executions>
                    <execution>
                        <id>jlupin-zip</id>
                        <goals>
                            <goal>zip</goal>
                        </goals>
                        <configuration>
                            <additionalFilesDirectories>
                                <param>additional-files</param>
                            </additionalFilesDirectories>
                        </configuration>
                    </execution>
                    <execution>
                        <id>jlupin-deploy</id>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

It is standard JLupin's Maven configuration. All dependencies are set to scope provided because they are available on server by default. Also directory with JavaScript sources is added to generated jar as resource, because it should be included in it but files are not compiled. This way we can read scripts easly.

Microservice code

Add two files with configuration: one for JLupin (JavaScriptHelloWorldJLupinConfiguration) and one for Spring container (JavaScriptHelloWorldSpringConfiguration). Create package com.example.configuration and put classed there.

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 JavaScriptHelloWorldJLupinConfiguration extends JLupinAbstractApplicationContainerProducer {
    public JLupinApplicationContainer produceJLupinApplicationContainer() {
        return new JLupinAbstractSpringApplicationContainer() {
            @Override
            public AbstractApplicationContext getAbstractApplicationContext() {
                return new AnnotationConfigApplicationContext(JavaScriptHelloWorldSpringConfiguration.class);
            }
        };
    }
}
package com.example.configuration;

import com.example.service.interfaces.ExampleService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;

import jdk.nashorn.api.scripting.NashornScriptEngineFactory;

@Configuration
@ComponentScan("com.example")
public class JavaScriptHelloWorldSpringConfiguration {
    @Bean("jLupinRegularExpressionToRemotelyEnabled")
    public List<String> getRemotelyBeanList() {
        final List<String> list = new ArrayList<>();
        list.add("exampleService");
        return list;
    }

    @Bean("exampleService")
    public ExampleService getExampleService() throws FileNotFoundException, ScriptException {
        return getService(ExampleService.class, "com/example/service/impl/ExampleServiceImpl.js");
    }

    private <T> T getService(Class<T> interfaceClass, final String implementationPath) throws FileNotFoundException, ScriptException {
        final InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(implementationPath);
        if (resourceAsStream == null) {
            throw new IllegalStateException("Cannot find implementation for interface " + interfaceClass.getName() + " and implementation path " + implementationPath + ".");
        }
        final InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream);

        final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(NashornScriptEngineFactory.class.getClassLoader());
        final ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");

        if (nashorn == null) {
            throw new IllegalStateException("Cannot find nashorn javascript engine.");
        }

        nashorn.eval(inputStreamReader);

        Invocable invocable = (Invocable) nashorn;
        return invocable.getInterface(interfaceClass);
    }
}

As you can see special function is defined in configuration to easly add new servcies defined in JavaScript. Because JavaScript files are also not annotated it is required to define service as a bean.

Microservice is configured but does nothing. Create two packages: com.example.service.interfaces in Java sources and com.example.service.impl in JavaScript sources. Put service definitions in them:

package com.example.service.interfaces;

import javax.script.ScriptException;

public interface ExampleService {
    String hello(final String name) throws ScriptException, NoSuchMethodException;
}
function hello(name) {
    return "Hello, " + name + "!";
}

Microservice is done. You only need to add configuration for it. Create special directory for it called additional-files. Put in there two files: configuration.yml and log4j2.xml.

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.JavaScriptHelloWorldJLupinConfiguration'
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
<?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/javascript-hello-world</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>

Microservice deployment

To deploy micrservice make sure that you have started JLupin Platform and just run command mvn clean package jlupin-platform:deploy. It will build our application, create deployable zip and deploy microservice to server.