项目作者: metarhia

项目描述 :
JSTP implementation in java
高级语言: Java
项目地址: git://github.com/metarhia/jstp-java.git
创建时间: 2016-08-23T10:17:16Z
项目社区:https://github.com/metarhia/jstp-java

开源协议:Other

下载


Java JSTP implementation

jstp js

Check for the latest JSTP version

Installation

Gradle:

Add this to your build.gradle (check for the latest version):

  1. dependencies {
  2. compile group: 'com.metarhia.jstp', name: 'jstp', version: '0.10.0'
  3. }

Maven:

  1. <dependency>
  2. <groupId>com.metarhia.jstp</groupId>
  3. <artifactId>jstp</artifactId>
  4. <version>0.10.0</version>
  5. <type>pom</type>
  6. </dependency>

Parser usage

JSParser will mostly convert to native java objects
(with a few exceptions where specific implementation was needed)

Few simple examples

  1. JSObject a = JSParser.parse("{a: 3, b: 2}");
  2. // Get field 'a' of object {a: 3, b: 2}
  3. a.get("a"); // returns 3
  4. // Get second field of object {a: 3, b: 2}
  5. a.getByIndex(1); // returns 2
  6. // But you can easily use it as a Map
  7. Map<String, Number> map = (Map<String, Number>) a;
  8. // or
  9. Map<String, Number> a = JSParser.parse("{a: 3, b: 2}");
  10. List<Double> arr = JSParser.parse("[1, 2, 3]");
  11. // Get second element of array [1, 2, 3];
  12. arr.get(1); // returns 2
  13. String str = JSParser.parse("'abc'");
  14. // str now equals to "abc"
  15. List<?> arr = JSParser.parse("[1,, 3]");
  16. // Get second element of array [1,, 3];
  17. arr.get(1); // returns JSUndefined

To serialize objects you can use JSSerializer

  1. List<Number> arr = new JSNativeParser("[1, 2, 3]").parse();
  2. JSSerializer.stringify(arr); // returns "[1,2,3]"
  3. Map<String, Number> a = JSParser.parse("{a: 3, b: 2}");
  4. JSSerializer.stringify(a); // returns "{a:3,b:2}"

If it doesn’t know how to serialize input it’ll be serialized as "undefined",
also you can define how your objects will be serialized via JSSerializable
interface.

They can be parsed in js with a simple eval statement or
jstp and mdsf

Connection

Establish connection

To establish JSTP connection, you need to provide transport.
As of now the only available transport is TCP. Optionally you can
define session policy (there are 2 basic ones in SDK:
DropSessionPolicy - which will create new connection every
time transport is restored and SimpleSessionPolicy - which
will resend cached messages and try to restore session.
SimpleSessionPolicy is used by default). For example:

  1. String host = "metarhia.com";
  2. int port = 80;
  3. Transport transport = new TCPTransport(host, port /*, useSSl == true */);
  4. Connection connection = new Connection(transport);

You can change used transport by calling useTransport() method.
This will close previous transport if available and set provided one
as current transport. It will try to connect and upon connection
appropriate method of session policy will be called when transport
reports that it’s connected.

To react to connection events, you can use ConnectionListener (there is
also SimpleConnectionListener version that defaults to ignoring all calls):

  1. connection.addListener(new ConnectionListener() {
  2. @Override
  3. public void onConnected(boolean restored) {
  4. // ...
  5. }
  6. @Override
  7. public void onConnectionClosed() {
  8. // ...
  9. }
  10. @Override
  11. public void onMessageRejected(JSObject message) {
  12. // ...
  13. }
  14. });

You can define applicationName and/or session Id when connecting,
or connect without them (you must at least once call connect
with application name before that):

  1. connection.connect();
  2. // ...
  3. connection.connect("applicationName");
  4. // ...
  5. connection.connect("applicationName", "sessionId");

JSTP message types

Handshake

Usually you don’t have to send handshake messages manually. You may need
them if If you need to implement your own session policy or change
transport on active connection. You can send handshake message as follows:

  1. // anonymous handshake message
  2. connection.handshake("applicationName", new ManualHandler() {
  3. @Override
  4. public void onMessage(JSObject message) {
  5. // ...
  6. }
  7. @Override
  8. public void onError(int errorCode) {
  9. // ...
  10. }
  11. });
  12. // handshake with attempt to restore session "sessionId"
  13. connection.handshake("applicationName", "sessionId", new ManualHandler() {
  14. @Override
  15. public void onMessage(JSObject message) {
  16. // ...
  17. }
  18. @Override
  19. public void onError(int errorCode) {
  20. // ...
  21. }
  22. });
  23. // handshake message with authorization (login and password)
  24. connection.handshake("applicationName", "name", "pass", new ManualHandler() {
  25. @Override
  26. public void onMessage(JSObject message) {
  27. // ...
  28. }
  29. @Override
  30. public void onError(int errorCode) {
  31. // ...
  32. }
  33. });

Call

To send call message:

  1. List<?> args = Arrays.asList('a', 'b', 'c');
  2. connection.call("interfaceName", "methodName", args, new ManualHandler() {
  3. @Override
  4. public void onMessage(JSObject message) {
  5. // ...
  6. }
  7. @Override
  8. public void onError(int errorCode) {
  9. // ...
  10. }
  11. );

Or you can use OkErrorHandler that will make this much simpler and clearer:

  1. List<?> args = Arrays.asList('a', 'b', 'c');
  2. connection.call("interfaceName", "methodName", args, new OkErrorHandler() {
  3. @Override
  4. public void handleOk(List<?> data) {
  5. // ...
  6. }
  7. @Override
  8. public void handleError(Integer errorCode, List<?> data) {
  9. // ...
  10. }
  11. );

To handle incoming call messages, you have to set setCallHandler() for that call.
There can only be one call handler for each call.

Callback

While sending callback you should specify callback type (JSCallback.OK or
JSCallback.ERROR) and arbitrary arguments.

  1. connection.setCallHandler("interfaceName", "methodName", new CallHandler() {
  2. @Override
  3. public void handleCall(String methodName, List<?> data) {
  4. // ...
  5. callback(connection, JSCallback.OK, Arrays.asList('Hello'));
  6. }
  7. });

You also can send callback messages like this:

  1. connection.callback(JSCallback.OK, args);
  2. // define custom message number
  3. Long customIndex;
  4. // ...
  5. connection.callback(JSCallback.OK, args, customIndex);

Inspect

Incoming inspect messages are handled by the Connection itself. To make
methods visible through inspect message you just need to define method
names with appropriate interfaces.

  1. connection.setClientMethodNames("interfaceName1", "methodName1", "methodName2");
  2. connection.setClientMethodNames("interfaceName2", "methodName1", "methodName2");
  3. // ...

To send inspect message:

  1. connection.inspect("interfaceName", new ManualHandler() {
  2. @Override
  3. public void onMessage(JSObject message) {
  4. // ...
  5. }
  6. @Override
  7. public void onError(int errorCode) {
  8. // ...
  9. }
  10. });

Event

To handle incoming events, you add event handlers with addEventHandler().
There can be multiple event handlers for each event.

  1. connection.addEventHandler("interfaceName", "methodName", new EventHandler() {
  2. @Override
  3. public void handleEvent(String eventName, List<?> data) {
  4. // ...
  5. }
  6. });

Sending event message:

  1. List<String> args = Arrays.asList('Hello');
  2. connection.event("interfaceName", "methodName", args);

Executable handler

If you want to handle incoming packets on a separate thread, you can use
ExecutableHandler instead of ManualHandler. It requires Executor to run
the packet handling method on it. Incoming message is a protected parameter in
ExecutableHandler. You can use it like this:

  1. Connection connection = ...;
  2. Executor executor = ...;
  3. connection.call("interfaceName", "methodName", new ArrayList<>(),
  4. new ExecutableHandler(executor, new OkErrorHandler() {
  5. @Override
  6. public void handleOk(List<?> data) {
  7. // ...
  8. }
  9. @Override
  10. public void handleError(Integer errorCode, List<?> data) {
  11. // ...
  12. }
  13. })
  14. });

JSTP compiler

JSTP compiler is a nice feature to ease handling of JSTP messages. You can
declare interfaces that correspond to specific API or simple call to avoid
writing all boilerplate code to get the arguments out of the message. JSTP
compiler will parse those at compile time and generate implementations.

Installation

Check for the latest version

Gradle:

  1. dependencies {
  2. compile group: 'com.metarhia.jstp', name: 'jstp-compiler', version: '0.4.0'
  3. }

Maven:

  1. <dependency>
  2. <groupId>com.metarhia.jstp</groupId>
  3. <artifactId>jstp-compiler</artifactId>
  4. <version>0.4.0</version>
  5. <type>pom</type>
  6. </dependency>

Be aware that compiler documentation is currently outdated and needs a refresh as some methods/usages have changed.

Handlers usage

JSTP handlers are used to process data from incoming JSTP messages, just like
usual ManualHandlers do. Unlike ManualHandler, you are able to customize
JSTP handlers as you wish, declaring methods with annotations described below.
To create your own handler, just add the @Handler annotation to the
required interface.

  1. @Handler
  2. public interface ExampleHandler {
  3. // ...
  4. }

You also can make your handler extended from ManualHandler or
ExecutableHandler setting base class as an annotation parameter. For example,
if you want the messages to be handled on a separate thread, you can set
ExecutableHandler.class as a @Handler parameter:

  1. @Handler(ExecutableHandler.class)
  2. public interface ExampleHandler {
  3. // ...
  4. }

With this annotation the generated class will respect ExecutableHandler
interface and generate its code in run() method instead of handle() method.

@NotNull

Annotates that the method should only be called if all of its arguments are not
null (in case of method-wide annotation) or only specific parameters are not
null (in case of argument-wide annotations)

  1. @Handler
  2. public interface ExampleHandler {
  3. // ...
  4. @NotNull
  5. void onExampleValue(List<?> args);
  6. // ...
  7. }
@Object

Gets the field of the received message by specified name. It also allows getting
elements from nested objects, the value will be retrieved in the order of keys
specified.

  1. @Handler
  2. public interface OkErrorHandler {
  3. // ...
  4. @NotNull
  5. @Object("ok")
  6. void onOK(List<?> args);
  7. @NotNull
  8. @Object("error")
  9. void onError(List<?> args);
  10. // gets String value by key "neededValue" in object got by "ok"
  11. @Object({"ok", "neededValue"})
  12. void onNeededValueRetrieved(String value);
  13. // ...
  14. }
@Array

Can be used to get the specific value from JSTP message. It also allows
getting elements from nested arrays, the value will be retrieved in the order
of indexes specified.

  1. @Handler
  2. public interface ExampleHandler {
  3. // ...
  4. void onFirstIndex(@Array(1) String arg);
  5. // gets (List<?>) message[1][2]
  6. void onValueBySecondIndex(@Array({1, 2}) List<?> args);
  7. // ...
  8. }
@Mixed

It is a sort of combination of @Object and @Array annotations. You can get
needed value by index or by key. It also allows getting elements from nested
objects and arrays, the value will be retrieved in the order of keys and
indexes specified. To get a value by key, you should just declare the required
key like in @Object annotation, for example "some key". To get value from
array by index, you can declare it as "[index]". To get an object value by
index (according to keys order) you should declare it as "{key index}".

  1. @Handler
  2. public interface ExampleHandler {
  3. // ...
  4. @Mixed("ok")
  5. void onNeededNamedValue(Object args);
  6. @Mixed("{1}")
  7. void onKeyByIndexValue(Object args);
  8. // gets message["ok"][1][2]
  9. @Mixed({"ok", "[1]", "{2}"})
  10. void onNeededMixValue(Object args);
  11. // ...
  12. }

You can use @Array, @Object, @Mixed annotations with both
methods and parameters. In case of method it’ll decide starting
value to get from (to apply getters) for other parameter-wide
getters. If no method-wide getter is specified the value
under second key of object’ll be used by default, to cancel
this behaviour you can either define you own getter or specify
@NoDefaultGet annotation (this way starting value’ll be
the jstp message itself).

After compilation class named like JSTP + (YourHandlerName) (for this example
it will be JSTPExampleHandler) will be generated and you will be able to use
it in message processing.

  1. connection.call("interfaceName", "methodName", args, new ExampleHandler() {
  2. // ...
  3. });

JSTP Receivers

You can process received values not only via single handler, but by several
ones. They can be added or removed from Receiver via addHandler() and
removeHandler() methods. JSTP receiver is generated similarly to Handler.
To generate receiver, you need to do the following:

  1. @Receiver
  2. public interface ExampleReceiver {
  3. // ...
  4. }

The syntax of declaring methods is the same as in Handler. After
compilation class named like JSTP + (Your receiver name) (for this example it
will be JSTPExampleReceiver) will be generated and you will be able to use it
in message processing.

You can use custom receiver like this:

  1. JSTPExampleReceiver receiver = new JSTPExampleReceiver();
  2. receiver.addHandler(new ExampleReceiver() {
  3. // ...
  4. });
  5. receiver.addHandler(new ExampleReceiver() {
  6. // ...
  7. });
  8. connection.call("interfaceName", "methodName", args, receiver);

@Proxy

@Proxy annotation allows you to easily auto-generate interface to the remote
server that will look as if you are calling local java methods. To create
a proxy, you just need to describe the interface with appropriate annotations.

To declare the proxy interface, you need to add the @Proxy annotation to the
interface definition. If you want your proxy to be a singleton, set the
singleton parameter of the annotation to true. If you want to set the
default interface name for the Connection methods, you can set it with
interfaceName parameter.

  1. // Creates singleton proxy with default interface name "defaultInterface"
  2. @Proxy(interfaceName = "defaultInterface", singleton = true)
  3. public interface MyProxy {
  4. // proxy methods
  5. }

For now, you can declare proxy wrappers for sending call and event packets.

Call

To create wrapper for call method, use @Call annotation. Set the method
name as an annotation parameter (the default interface will be used), or
interface name and method name. The method name is not required as well: if you
use @Call annotation without parameters at all, your Java method name will be
used as a call method name. Specify the arguments and callback handler
(if they are needed) as your method parameters. See the examples:

  1. @Proxy(interfaceName = "defaultInterface")
  2. public interface MyProxy {
  3. // Makes a call with specified "interfaceName" and "methodName", sets "param"
  4. // and "otherParam" as call arguments and uses "hander" to handle method
  5. // callback
  6. @Call({"interfaceName", "methodName"})
  7. void callInterfaceMethod(String param, int otherParam, ManualHandler handler);
  8. // Makes a call with default interface name and method name "methodName"
  9. // without arguments and uses "hander" to handle method callback
  10. @Call("methodName")
  11. void callDefaultInterfaceMethod(ManualHandler handler);
  12. // Makes a call with default interface name and method name "otherMethodName"
  13. // without arguments and without callback handler
  14. @Call("otherMethodName")
  15. void callNoParametersMethod();
  16. // Makes a call with default interface name and method name joinChat
  17. // without arguments and uses handler to handle method callback
  18. @Call()
  19. void joinChat(ManualHandler handler);
  20. }
Event

To create wrapper for event method, use @Event annotation. Set the method
name as an annotation parameter (the default interface will be used), or
interface name and method name. The event name is not required as well: if you
use @Event annotation without parameters at all, your Java method name will be
used as an event name. Specify the arguments (if they are needed) as
your method parameters. See the examples:

  1. @Proxy(interfaceName = "defaultInterface")
  2. public interface MyProxy {
  3. // Sends event with specified interface name "interfaceName" and event name
  4. // "eventName" without parameters
  5. @Event({"interfaceName", "eventName"})
  6. void sendInterfaceEvent();
  7. // Sends event with default interface name and event name "eventName" with
  8. // specified parameters
  9. @Event("eventName")
  10. void sendEvent(String param);
  11. // Sends event with default interface name and event name
  12. // onSomethingHappened without parameters
  13. @Event()
  14. void onSomethingHappened();
  15. }

After compilation class named like JSTP + (Your proxy name) (for this example
it will be JSTPMyProxy) will be generated. See some usage examples:

  1. Connection connection;
  2. Executor executor;
  3. // ...
  4. JSTPMyProxy proxy = new JSTPMyProxy(connection);
  5. // calls method and handles callback by manual handler
  6. proxy.callDefaultInterfaceMethod(new ManualHandler() {
  7. @Override
  8. public void handle(JSObject jsObject) {
  9. // handle result
  10. }
  11. });
  12. // calls method and handles callback by handler created with @Handler annotation
  13. // (see the example of generating handlers)
  14. proxy.callDefaultInterfaceMethod(new JSTPOkErrorHandler() {
  15. @Override
  16. public void onOk(List<?> args) {
  17. // handle data
  18. }
  19. @Override
  20. public void onError(Integer errorCode) {
  21. // handle error code
  22. }
  23. });
  24. // calls method and handles callback by executable handler (see the example of
  25. // executable handler)
  26. proxy.callDefaultInterfaceMethod(new ExecutableHandler(executor) {
  27. @Override
  28. public void run() {
  29. // handle message received
  30. }
  31. });
  32. // sends event with specified parameters
  33. proxy.sendEvent("myParam");
Call handler

You can process incoming call packets by setting call handlers for called
methods, just like if you did it with Connection directly. You can set call
handler by setCallHandler() method, and remove your handler by
removeCallHandler() method. See the examples:

  1. // sets manual call handler for "methodName" of "interfaceName"
  2. proxy.setCallHandler("interfaceName", "methodName", new ManualHandler() {
  3. @Override
  4. public void handle(JSObject jsObject) {
  5. // handle object
  6. }
  7. });
  8. // sets executable call handler for "methodName" of "interfaceName" (see the
  9. // example of executable handler)
  10. proxy.setCallHandler("interfaceName", "methodName",
  11. new ExecutableHandler(executor) {
  12. @Override
  13. public void run() {
  14. // handle message received
  15. }
  16. });

If we want to use handler created with @Handler annotation like this

  1. @Handler
  2. public interface OkErrorHandler {
  3. @NotNull
  4. @Object("ok")
  5. void onOk(List<?> args);
  6. @NotNull
  7. @Object("error")
  8. void onError(@Array(0) Integer errorCode);
  9. }

We can use generated JSTPOkErrorHandler as a call handler for proxy:

  1. proxy.setCallHandler("interfaceName", "methodName", new JSTPOkErrorHandler() {
  2. @Override
  3. public void onOk(List<?> args) {
  4. // handle data
  5. }
  6. @Override
  7. public void onError(Integer errorCode) {
  8. // handle error code
  9. }
  10. });
Event handler

You can process incoming event packets by adding event handlers, just like
if you did it with Connection directly. You can add event handler by
addEventHandler() method, and remove your handler by removeEventHandler()
method. See the examples:

  1. // adds manual event handler for "eventName" of "interfaceName"
  2. proxy.addEventHandler("interfaceName", "eventName", new ManualHandler() {
  3. @Override
  4. public void handle(JSObject jsObject) {
  5. // handle object
  6. }
  7. });
  8. // adds executable handler for "eventName" of "interfaceName" (see the example
  9. // of executable handler)
  10. proxy.addEventHandler("interfaceName", "eventName",
  11. new ExecutableHandler(executor) {
  12. @Override
  13. public void run() {
  14. // handle message received
  15. }
  16. });

If we want to use handler created with @Handler annotation like this

  1. @Handler
  2. public interface EventHandler {
  3. @Object("onMessage")
  4. void onMessage(@Array(0) String receivedMessage);
  5. }

We can use generated JSTPEventHandler as an event handler for proxy:

  1. proxy.addEventHandler("interfaceName", "eventName", new JSTPEventHandler() {
  2. @Override
  3. public void onMessage(String receivedMessage) {
  4. // handle message
  5. }
  6. });

If we modify the annotation in such a way:

  1. @Handler(ExecutableHandler.class)
  2. public interface EventHandler {
  3. @Object("onMessage")
  4. void onMessage(@Array(0) String receivedMessage);
  5. }

We can use handler with preferred executor:

  1. Executor executor;
  2. // ...
  3. proxy.addEventHandler("interfaceName", "eventName",
  4. new JSTPEventHandler(executor) {
  5. @Override
  6. public void onMessage(String receivedMessage) {
  7. // handle message
  8. }
  9. });