TIBCO EBX®
Documentation > Developer Guide
Navigation modeDocumentation > Developer Guide

REST Toolkit

Introduction

TIBCO EBX® offers the possibility to develop custom REST services using the REST Toolkit. The REST Toolkit supports JAX-RS 2.1 (JSR-370) and JSON-B (JSR-367).

A REST service is implemented by a Java class and its operations are implemented by Java methods. The response can be generated by serializing POJO objects. The request input can be unserialized to POJOs. Various input and output formats, including JSON, are supported. For more details on supported formats, see media types.

Rest Toolkit supports the following:

Application definitions

An EBX® module, that includes custom REST services, must provide at least one REST Toolkit application class. A REST Toolkit application class extends the EBX® RESTApplicationAbstract class. The minimum requirement is to define the base URL, using the @ApplicationPath annotation and the set of packages to scan for REST service classes.

Note

Only packages accessible from the web application's classloader can be scanned.

Note

It is possible to register REST resource classes or singletons, packaged inside or outside the web application archive, through the ApplicationConfigurator.register(java.lang.Class) or ApplicationConfigurator.register(java.lang.Object) methods.

Note

If no packages scope is defined, then every class reachable from the web application's classloader will be scanned.

The application path cannot be "/" and must not collide with an existing resource from the module. It is recommended to use "/rest" (the value of the RESTApplicationAbstract.REST_DEFAULT_APPLICATION_PATH constant).

EBX® Documentation annotation is optional. It is displayed to administrators in 'Technical configuration' > 'Modules and data models' or when logging and debugging.

import javax.ws.rs.*;

import com.orchestranetworks.rest.*;
import com.orchestranetworks.rest.annotation.*;

@ApplicationPath(RESTApplicationAbstract.REST_DEFAULT_APPLICATION_PATH)
@Documentation("My REST sample application")
public final class RESTApplication extends RESTApplicationAbstract
{
   public RESTApplication()
   {
      // Adds one or more package names which will be used to scan for components.
      super((cfg) -> cfg.addPackages(RESTApplication.class.getPackage()));
   }
}

Service and operation definitions

A REST Toolkit service is implemented by a Java class and its operations are implemented by its methods.

Class and methods can be annotated by @Path to specify the access path. The @Path annotation value defined at the class level will prepend the ones defined on methods. Warning, only one @Path annotation is allowed per class or method.

Media types accepted and produced by a resource are respectively defined by the @Consumes and @Produces JAX-RS annotations. The supported media types are:

Valid HTTP(S) methods are specified by JAX-RS annotations @GET, @POST, @PUT, etc. Only one of these annotations can be set on each Java method (this means that a Java method can support only one HTTP method).

Warning: URL parameters with a name prefixed with ebx- are reserved by REST Toolkit and should not be defined by custom REST services, unless explicitly authorized by the EBX® documentation.

URL and sample

The REST URL to access the description service for the sample is defined below:

http[s]://<host>[:<port>]/<path to webapp>/rest/track/v1/description

Where:

Note

Please note that /rest/track/v1/description corresponds to the concatenation of the application's @ApplicationPath and service's @Path annotations.

The following REST Toolkit service sample provides methods to query and manage track data:

import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import java.util.stream.*;

import javax.servlet.http.*;
import javax.ws.rs.*;
import javax.ws.rs.container.*;
import javax.ws.rs.core.*;

import com.orchestranetworks.rest.annotation.*;
import com.orchestranetworks.rest.inject.*;

/**
 * The REST Toolkit Track service v1.
 */
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/track/v1")
@Documentation("Track service")
public final class TrackService
{
	@Context
	private ResourceInfo resourceInfo;

	@Context
	private SessionContext sessionContext;

	private static final Map<Integer, TrackDTO> TRACKS = new ConcurrentHashMap<>();

	/**
	 * Gets service description
	 */
	@GET
	@Path("/description")
	@Documentation("Gets service description")
	@Produces({ MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
	@AnonymousAccessEnabled
	public String handleServiceDescription()
	{
		return this.resourceInfo.getResourceMethod().getAnnotation(Documentation.class).value();
	}

	/**
	 * Selects tracks.
	 */
	@GET
	@Path("/tracks")
	@Documentation("Selects tracks")
	public Collection<TrackDTO> handleSelectTracks(
			@QueryParam("singerFilter") final String singerFilter, // a URL parameter holding a Java regular expression
			@QueryParam("titleFilter") final String titleFilter) // a URL parameter holding a Java regular expression
	{
		final Pattern singerPattern = TrackService.compilePattern(singerFilter);
		final Pattern titlePattern = TrackService.compilePattern(titleFilter);

		return TRACKS.values()
				.parallelStream()
				.filter(Objects::nonNull)
				.filter(track -> singerPattern == null || singerPattern.matcher(track.singer).matches())
				.filter(track -> titlePattern == null || titlePattern.matcher(track.title).matches())
				.collect(Collectors.toList());
	}

	private static Pattern compilePattern(final String aPattern)
	{
		if (aPattern == null || aPattern.isEmpty())
			return null;

		try
		{
			return Pattern.compile(aPattern);
		}
		catch (final PatternSyntaxException ignore)
		{
			// ignore invalid pattern
			return null;
		}
	}

	/**
	 * Counts all tracks.
	 */
	@GET
	@Path("/tracks:count")
	@Documentation("Counts all tracks")
	public int handleCountTracks()
	{
		return TRACKS.size();
	}

	/**
	 * Selects a track by id.
	 */
	@GET
	@Path("/tracks/{id}")
	@Documentation("Selects a track by id")
	public TrackDTO handleSelectTrackById(@PathParam("id") Integer id)
	{
		final TrackDTO track = TRACKS.get(id);
		if (track == null)
			throw new NotFoundException("Track id [" + id + "] does not found.");
		return track;
	}

	/**
	 * Deletes a track by id.
	 */
	@DELETE
	@Path("/tracks/{id}")
	@Documentation("Deletes a track by id")
	public void handleDeleteTrackById(@PathParam("id") Integer id)
	{
		if (!TRACKS.containsKey(id))
			throw new NotFoundException("Track id [" + id + "] does not found.");
		TRACKS.remove(id);
	}

	/**
	 * Inserts or updates one or several tracks.
	 * <p>
	 * The complex response structure corresponds to one of:
	 * <ul>
	 *  <li>An empty content with the <code>location<code> HTTP header defined
	 *   to the access URI.</li>
	 *  <li>A JSON array of {@link ResultDetailsDTO} objects.</li>
	 * </ul>
	 */
	@POST
	@Path("/tracks")
	@Documentation("Inserts or updates one or several tracks")
	public Response handleInsertOrUpdateTracks(List<TrackDTO> tracks)
	{
		int inserted = 0;
		int updated = 0;

		final ResultDetailsDTO[] resultDetails = new ResultDetailsDTO[tracks.size()];
		int resultIndex = 0;

		final URI base = this.sessionContext.getURIInfoUtility()
				.createBuilderForRESTApplication()
				.path(this.getClass())
				.segment("tracks")
				.build();

		for (final TrackDTO track : tracks)
		{
			final String id = String.valueOf(track.id);
			final URI uri = UriBuilder.fromUri(base).segment(id).build();

			final int code;
			if (TRACKS.containsKey(track.id))
			{
				code = HttpServletResponse.SC_NO_CONTENT;
				updated++;
			}
			else
			{
				code = HttpServletResponse.SC_CREATED;
				inserted++;
			}

			TRACKS.put(track.id, track);

			resultDetails[resultIndex++] = ResultDetailsDTO.create(
				code,
				null,
				String.valueOf(track.id),
				uri);
		}

		if (inserted == 1 && updated == 0)
			return Response.created(resultDetails[0].details).build();

		return Response.ok().entity(resultDetails).build();
	}

	/**
	 * Updates one track.
	 */
	@PUT
	@Path("/tracks/{id}")
	@Documentation("Update one track")
	public void handleUpdateOneTrack(@PathParam("id") Integer id, TrackDTO aTrack)
	{
		final TrackDTO track = TRACKS.get(id);
		if (track == null)
			throw new NotFoundException("Track id [" + id + "] does not found.");

		if (aTrack.id != null && !aTrack.id.equals(track.id))
			throw new BadRequestException("Selected track id [" + id
				+ "] is not equals to body track id.");

		TRACKS.put(aTrack.id, aTrack);
	}
}

This REST service uses the following Java classes, which represent a Data Transfer Objects (DTO), to serialize and deserialize data:

/**
 * DTO for a track.
 */
public final class TrackDTO
{
	public Integer id;
	public String singer;
	public String title;
}
import java.net.*;

/**
 * DTO for result details.
 */
@JsonbPropertyOrder({ "code", "label", "foreignKey", "details" })
public final class ResultDetailsDTO
{
	public int code;
	public String label;
	public String foreignKey;
	public URI details;

	public static ResultDetailsDTO create(
		final int aCode,
		final String aForeignKey,
		final URI aDetails)
	{
		return ResultDetailsDTO.create(aCode, null, aForeignKey, aDetails);
	}

	public static ResultDetailsDTO create(
		final int aCode,
		final String aLabel,
		final String aForeignKey,
		final URI aDetails)
	{
		final ResultDetailsDTO result = new ResultDetailsDTO();
		result.code = aCode;
		result.label = aLabel;
		result.foreignKey = aForeignKey;
		result.details = aDetails;
		return result;
	}
}

Authentication and lookup mechanism

A custom REST service developed with REST Toolkit supports the same authentication methods and lookup mechanism as the built-in REST data services. However, there is a slight difference concerning the 'Anonymous authentication Scheme' since its scope can be wider by using the AnonymousAccessEnabled. See REST authentication and permissions for more information.

REST authentication and permissions

By default, every REST resource Java method requires users to be authenticated.

However, some methods may need to be accessible anonymously. These methods must be annotated by AnonymousAccessEnabled.

Some methods may need to be restricted to given profiles. These methods may be annotated by Authorization to specify an authorization rule. An authorization rule is a Java class that implements the AuthorizationRule interface.

import javax.ws.rs.*;

import com.orchestranetworks.rest.annotation.*;

/**
 * The REST Toolkit service v1.
 */
@Path("/service/v1")
@Documentation("Service")
public final class Service
{
	...

	/**
	 * Gets service description
	 */
	@GET
	@AnonymousAccessEnabled
	public String handleServiceDescription()
	{
		...
	}

	/**
	 * Gets restricted service
	 */
	@GET
	@Authorization(IsUserAuthorized.class)
	public RestrictedServiceDTO handleRestrictedService()
	{
		...
	}
}

URI builders

REST Toolkit provides an utility interface URIInfoUtility to generate URIs. An instance of this interface is accessible through the injectable built-in object SessionContext.

Exception handling

A REST Toolkit Java method can produce a standard HTTP error response by throwing a Java exception that extends the JAX-RS class javax.ws.rs.WebApplicationException. JAX-RS defines exceptions for various HTTP status codes. EBX® defines UnprocessableEntityException that adds support for the HTTP 422(Unprocessable entity) code.

Plain Java exceptions are mapped to the HTTP status code 500 (Internal server error).

{
  "userMessage": "...", // Mandatory localized message
  "errorCode":   "999", // EBX® error code (optional, used mainly for HTTP error 422)
  "errors": [           // Internal messages useful when debugging (optional).
                        // Usually not displayed to the user and not localized.
     "Message 1", "Message 2" }
  ]
}

Monitoring

REST Toolkit events monitoring is similar to the data services log configuration. The difference is the property key which must be ebx.log4j.category.log.restServices.

Some additional properties are available to configure the log messages. See Configuring REST toolkit services for further information.

Packaging and registration

All applications and components are required to be packaged into the module's Web Application (war file).

The JAX-RS libraries, except the JAX-RS client API, are already included in ebx.jar and must not be packaged in the war file.

See Java EE deployment for more information.

The registration of a REST Toolkit application is integrated into the EBX® module registration process. The registration class must extend ModuleRegistrationListener, declare the Servlet 3.0 annotation @WebListener and override the handleContextInitialized method.

See Module registration for more information.

import javax.servlet.annotation.*;

import com.orchestranetworks.module.*;

@WebListener
public final class RegistrationModule extends ModuleRegistrationListener
{
	@Override
	public void handleContextInitialized(final ModuleInitializedContext aContext)
	{
		// Registers dynamically a REST Toolkit application.
		aContext.registerRESTApplication(RESTApplication.class);
	}
}
Documentation > Developer Guide