Tip
|
a test environment is available on Heroku and browable using Talend Component Kit Server Restlet Studio instance. |
The HTTP API intends to expose over HTTP most of Talend Component features, it is a standalone Java HTTP server.
Tip
|
WebSocket protocol is activated for the endpoints as well, instead of /api/v1 they uses the base /websocket/v1 , see WebSocket part for more details.
|
Here is the API:
Important
|
to ensure the migration can be activated you need to set in the execution configuration you send to the server
the version it was created with (component version, it is in component detail endpoint) with the key tcomp::component::version .
|
If some endpoints are intended to disappear they will be deprecated. In practise it means a header X-Talend-Warning
will be returned with some message as value.
You can connect on any endpoint replacing /api
by /websocket
and appending /<http method>
for the URL and formatting the request as:
SEND
destination: <endpoint after v1>
<headers>
<payload>^@
For instance:
SEND
destination: /component/index
Accept: application/json
^@
The response is formatted as follow:
MESSAGE
status: <http status code>
<headers>
<payload>^@
Tip
|
if you have a doubt about the endpoint, they are all logged during startup and you can find them in the logs. |
If you don’t want to create a pool of connection per endpoint/verb you can use the bus endpoint: /websocket/v1/bus
.
This endpoint requires that you add the header destinationMethod
to each request with the verb value - default would be GET
:
SEND
destination: /component/index
destinationMethod: GET
Accept: application/json
^@
Using the server zip (or Docker image)
you can configure HTTPS adding properties to MEECROWAVE_OPTS
. Assuming you have a certificate in /opt/certificates/component.p12
(don’t forget to add/mount it in the Docker image if you use it) you can activate it through:
# use -e for Docker
#
# this will skip the http port binding to only bind https on the port 8443 and setup the right certificate
export MEECROWAVE_OPTS="-Dskip-http=true -Dssl=true -Dhttps=8443 -Dkeystore-type=PKCS12 -Dkeystore-alias=talend -Dkeystore-password=talend -Dkeystore-file=/opt/certificates/component.p12"
component-form
library provides a way to build a component REST API facade compatible with react form library.
A trivial facade can be:
@Path("tacokit-facade")
@ApplicationScoped
public class ComponentFacade {
private static final String[] EMPTY_ARRAY = new String[0];
@Inject
private Client client;
@Inject
private ActionService actionService;
@Inject
private UiSpecService uiSpecService;
@Inject // assuming it is available in your app, use any client you want
private WebTarget target;
@POST
@Path("action")
public void action(@Suspended final AsyncResponse response, @QueryParam("family") final String family,
@QueryParam("type") final String type, @QueryParam("action") final String action,
final Map<String, Object> params) {
client.action(family, type, action, params).handle((r, e) -> {
if (e != null) {
onException(response, e);
} else {
response.resume(actionService.map(type, r));
}
return null;
});
}
@GET
@Path("index")
public void getIndex(@Suspended final AsyncResponse response,
@QueryParam("language") @DefaultValue("en") final String language) {
target
.path("component/index")
.queryParam("language", language)
.request(APPLICATION_JSON_TYPE)
.rx()
.get(ComponentIndices.class)
.toCompletableFuture()
.handle((index, e) -> {
if (e != null) {
onException(response, e);
} else {
index.getComponents().stream().flatMap(c -> c.getLinks().stream()).forEach(
link -> link.setPath(link.getPath().replaceFirst("/component/", "/application/").replace(
"/details?identifiers=", "/detail/")));
response.resume(index);
}
return null;
});
}
@GET
@Path("detail/{id}")
public void getDetail(@Suspended final AsyncResponse response,
@QueryParam("language") @DefaultValue("en") final String language, @PathParam("id") final String id) {
target
.path("component/details")
.queryParam("language", language)
.queryParam("identifiers", id)
.request(APPLICATION_JSON_TYPE)
.rx()
.get(ComponentDetailList.class)
.toCompletableFuture()
.thenCompose(result -> uiSpecService.convert(result.getDetails().iterator().next()))
.handle((result, e) -> {
if (e != null) {
onException(response, e);
} else {
response.resume(result);
}
return null;
});
}
private void onException(final AsyncResponse response, final Throwable e) {
final UiActionResult payload;
final int status;
if (WebException.class.isInstance(e)) {
final WebException we = WebException.class.cast(e);
status = we.getStatus();
payload = actionService.map(we);
} else if (CompletionException.class.isInstance(e)) {
final CompletionException actualException = CompletionException.class.cast(e);
log.error(actualException.getMessage(), actualException);
status = Response.Status.BAD_GATEWAY.getStatusCode();
payload = actionService.map(new WebException(actualException, -1, emptyMap()));
} else {
log.error(e.getMessage(), e);
status = Response.Status.BAD_GATEWAY.getStatusCode();
payload = actionService.map(new WebException(e, -1, emptyMap()));
}
response.resume(new WebApplicationException(Response.status(status).entity(payload).build()));
}
}
Note
|
the Client can be created using ClientFactory.createDefault(System.getProperty("app.components.base", "http://localhost:8080/api/v1"))
and the service can be a simple new UiSpecService() . The factory uses JAX-RS if the API is available (assuming a JSON-B provider is registered) otherwise it tries to use Spring.
|
All the conversion between component model (REST API) and uiSpec model is done through the UiSpecService
. It is based on the object model
which will be mapped to a ui model. The advantage to have a flat model in the component REST API is to make these layers easy to customize.
You can completely control the available components, tune the rendering switching the uiSchema
if desired or add/remove part of the form.
You can also add custom actions/buttons for specific needs of the application.
Note
|
the /migrate endpoint has nothing special so was not shown in previous snippet but if you need it you must add it as well.
|
<dependency>
<groupId>org.talend.sdk.component</groupId>
<artifactId>component-form-model</artifactId>
<version>${talend-component-kit.version}</version>
</dependency>
This maven dependency provides the UISpec model classes. You can use the Ui
API (with or without the builders)
to create UiSpec representations.
Example:
final Ui form1 = ui()
// (1)
.withJsonSchema(JsonSchema.jsonSchemaFrom(Form1.class).build())
// (2)
.withUiSchema(uiSchema()
.withKey("multiSelectTag")
.withRestricted(false)
.withTitle("Simple multiSelectTag")
.withDescription("This datalist accepts values that are not in the list of suggestions")
.withWidget("multiSelectTag")
.build())
// (3)
.withProperties(myFormInstance)
.build();
// (4)
final String json = jsonb.toJson(form1);
-
We extract the
JsonSchema
from reflection on the classForm1
. Note that@JsonSchemaIgnore
allows to ignore a field and@JsonSchemaProperty
allows to rename a property, -
We build programmatically using the builder API a
UiSchema
, -
We pass an instance of the form to let the serializer extracts it JSON model,
-
We serialize the
Ui
model which can be used by UiSpec compatible front widgets.
Important
|
the model uses JSON-B API to define the binding, ensure to have an implementation in your classpath. This can be done adding these dependencies: |
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jsonb_1.0_spec</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-json_1.1_spec</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-jsonb</artifactId>
<version>${johnzon.version}</version> <!-- 1.1.5 for instance -->
</dependency>
Default javascript integration goes through Talend UI Forms library.
It is bundled as a NPM module called component-kit.js
. It provides a default trigger implementation for the UIForm
.
Here is how to use it:
import React from 'react';
import UIForm from '@talend/react-forms/lib/UIForm/UIForm.container';
import TalendComponentKitTrigger from 'component-kit.js';
export default class ComponentKitForm extends React.Component {
constructor(props) {
super(props);
this.trigger = new TalendComponentKitTrigger({ url: '/api/to/component/server/proxy' });
this.onTrigger = this.onTrigger.bind(this);
// ...
}
onTrigger(event, payload) {
return this.trigger.onDefaultTrigger(event, payload);
}
// ...
render() {
if(! this.state.uiSpec) {
return (<div>Loading ...</div>);
}
return (
<UIForm
data={this.state.uiSpec}
onTrigger={this.onTrigger}
onSubmit={this.onSubmit}
/>
);
}
}
The logging uses Log4j2, you can specify a custom configuration using the system property -Dlog4j.configurationFile
or adding a log4j2.xml
file into the classpath.
Here are some common configurations:
-
Console logging:
<?xml version="1.0"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss.SSS}][%highlight{%-5level}][%15.15t][%30.30logger] %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
This outputs messages looking like:
[16:59:58.198][INFO ][ main][oyote.http11.Http11NioProtocol] Initializing ProtocolHandler ["http-nio-34763"]
-
JSON logging:
<?xml version="1.0"?>
<Configuration status="INFO">
<Properties>
<!-- DO NOT PUT logSource there, it is useless and slow -->
<Property name="jsonLayout">{"severity":"%level","logMessage":"%encode{%message}{JSON}","logTimestamp":"%d{ISO8601}{UTC}","eventUUID":"%uuid{RANDOM}","@version":"1","logger.name":"%encode{%logger}{JSON}","host.name":"${hostName}","threadName":"%encode{%thread}{JSON}","stackTrace":"%encode{%xThrowable{full}}{JSON}"}%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${jsonLayout}"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Output messages look like:
{"severity":"INFO","logMessage":"Initializing ProtocolHandler [\"http-nio-46421\"]","logTimestamp":"2017-11-20T16:04:01,763","eventUUID":"8b998e17-7045-461c-8acb-c43f21d995ff","@version":"1","logger.name":"org.apache.coyote.http11.Http11NioProtocol","host.name":"TLND-RMANNIBUCAU","threadName":"main","stackTrace":""}
-
Rolling file appender
<?xml version="1.0"?>
<Configuration status="INFO">
<Appenders>
<RollingRandomAccessFile name="File" fileName="${LOG_PATH}/application.log" filePattern="${LOG_PATH}/application-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="[%d{HH:mm:ss.SSS}][%highlight{%-5level}][%15.15t][%30.30logger] %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="100 MB" />
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
More details are available on RollingFileAppender documentation.
Tip
|
of course you can compose previous layout (message format) and appenders (where logs are written). |
The server module contains several configuration you can set in:
-
Environment variables
-
System properties
-
A file located based on the
--component-configuration
CLI option