Skip to main content
Jump to: navigation, search

Difference between revisions of "Tutorial: Exposing a Jax REST service as an OSGi Remote Service"

Line 121: Line 121:
 
For this tutorial we will use a service for accessing a simple database of Students.  [https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.webapp.host/src/com/mycorp/examples/student/webapp/host/StudentResource.java Here is a Jax-RS implementation class].  Notice the annotations on the public methods.  These annotations signal to the Jax-RS implementation that these methods are to be exposed as remote services.  When included in a webapp (war file) with the necessary Jax-RS implementation libraries and dependencies on (e.g.) a Tomcat server, this service would be exposed for web-based remote access.
 
For this tutorial we will use a service for accessing a simple database of Students.  [https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.webapp.host/src/com/mycorp/examples/student/webapp/host/StudentResource.java Here is a Jax-RS implementation class].  Notice the annotations on the public methods.  These annotations signal to the Jax-RS implementation that these methods are to be exposed as remote services.  When included in a webapp (war file) with the necessary Jax-RS implementation libraries and dependencies on (e.g.) a Tomcat server, this service would be exposed for web-based remote access.
  
In order to make this student database service available for clients/consumers as an OSGi Remote Service, it's useful to abstract an interface with the appropriate methods and including the same Jax-RS annotations.  Here is such an interface for the StudentResouce
+
In order to make this student database service available for clients/consumers as an OSGi Remote Service, it's useful to abstract an interface with the appropriate methods and including the same Jax-RS annotations.  Here is such an interface for the StudentResource
  
 
<source lang="java">
 
<source lang="java">
Line 155: Line 155:
 
</source>
 
</source>
  
Please note:  the method signatures and annotations are '''exactly''' the same as those given on the actual [https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.webapp.host/src/com/mycorp/examples/student/webapp/host/StudentResource.java Jax-RS service resource class].  A bundle project with this service interface and dependencies is [https://github.com/ECF/JaxRSProviders/tree/master/examples/com.mycorp.examples.student provided here].
+
[https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.webapp.host/src/com/mycorp/examples/student/webapp/host/StudentResource.java Here is the complete interface].
  
Note also that the annotations are only those standardized by the Jax-RS specification (i.e. in javax.ws.rs.* packages), and so are '''not''' bound to any Jax-RS implementation.  
+
Note:  the method signatures and annotations are '''exactly''' the same as those given on the actual [https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.webapp.host/src/com/mycorp/examples/student/webapp/host/StudentResource.java Jax-RS service resource class].  The full example bundle project with this service interface and dependencies is [https://github.com/ECF/JaxRSProviders/tree/master/examples/com.mycorp.examples.student provided here].
 +
 
 +
Note also that the annotations are only those standardized by the Jax-RS specification (i.e. in javax.ws.rs.* packages), and so are '''not''' bound to any Jax-RS implementation. This allows the use of a variety of JAX-RS implementations (Jersey, Apache CXF, RestEasy, others), '''without''' having to change the StudentService method signatures or annotations.
  
 
With this interface and the [https://github.com/ECF/JaxRSProviders/tree/master/bundles ECF Remote Service Jax-RS provider bundles] and their dependencies, we can create a client/consumer application that uses this remote service.  ECF's implementation will dynamically create a proxy implementation of the StudentService interface, and make it available to the consumer.
 
With this interface and the [https://github.com/ECF/JaxRSProviders/tree/master/bundles ECF Remote Service Jax-RS provider bundles] and their dependencies, we can create a client/consumer application that uses this remote service.  ECF's implementation will dynamically create a proxy implementation of the StudentService interface, and make it available to the consumer.
  
As an OSGi service, it can be discovered and made available in several ways, but the easiest is to have the proxy instance injected via Declarative Services.  Here is the java code for a Declarative Services component that dynamically injects the StudentService
+
As an OSGi service, it can be discovered and made available in several ways, but the easiest is to have the proxy instance injected via Declarative Services.  Here is the java code for a DS component that injects the StudentService into the client code:
  
 
<source lang="java">
 
<source lang="java">
Line 201: Line 203:
 
</source>
 
</source>
  
When called by the DS/SCR runtime, this code in the bindStudentService method invokes all the available StudentService proxy methods.  At that time, these method calls will be automatically turned into valid Jax-RS client calls by the proxy.
+
When called by the DS/SCR runtime, this code in the bindStudentService method invokes the StudentService proxy methods.  At that time, these method calls will be turned into valid Jax-RS client calls by the ECR-created proxy.
  
==Remote Service Discovery==
+
==Jax-RS Student Service Server==
  
There are several ways to discover an OSGi Remote Service, but for the purposes of this tutorialwe will use EDEF (Endpoint Description Extension Format), an OSGi-standardized xml-based file format for describing an OSGi Remote Service.  See [[File-based Discovery | Static File-based Discovery of Remote Service Endpoints]] for background on EDEF.  For this tutorial [https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.client.filediscovery/studentserviceEDEF.xml here] is the EDEF for this service, and [https://github.com/ECF/JaxRSProviders/tree/master/examples/com.mycorp.examples.student.client.filediscovery here] is the project containing this EDEF.
+
Here is the StudentService server code
  
==Jax-RS Student Service Server==
+
<source lang="java">
 +
// The jax-rs path annotation for this service
 +
@Path("/studentservice")
 +
// The OSGi DS (declarative services) component annotation. Note that the
 +
// /rsexport.properties file defines this service impl as a remote service
 +
// and configures the usage of the Jersey Jax-RS implementation as the
 +
// desired distribution provider. See /rsexport.jersey.properties
 +
@Component(immediate = true, properties = "rsexport.jersey.properties")
 +
public class StudentServiceImpl implements StudentService {
  
For testing, [https://github.com/ECF/JaxRSProviders/tree/master/examples/com.mycorp.examples.student.webapp.host this project] was used to create a Tomcat 7 server (no OSGi) with a webapp that exposes the StudentResource as a Jax-RS service using JBoss' Resteasy implementation. Notice that in this case the server is not using OSGi, and is using the Resteasy Jax-RS implementation, rather than the Jersey implementation used on the client.
+
...
 +
// Provide a map-based storage of students
 +
private static Map<String, Student> students = Collections.synchronizedMap(new HashMap<String, Student>());
 +
// Create a single student and add to students map
 +
static {
 +
Student s = new Student("Joe Senior");
 +
s.setId(UUID.randomUUID().toString());
 +
s.setGrade("First");
 +
Address a = new Address();
 +
a.setCity("New York");
 +
a.setState("NY");
 +
a.setPostalCode("11111");
 +
a.setStreet("111 Park Ave");
 +
s.setAddress(a);
 +
students.put(s.getId(), s);
 +
}
  
==Running the Jax-RS OSGi Service Client==
+
// Implementation of StudentService based upon the students map
 +
@GET
 +
@Produces(MediaType.APPLICATION_XML)
 +
@Path("/students")
 +
public List<Student> getStudents() {
 +
return new ArrayList<Student>(students.values());
 +
}
  
Triggering the RSA discovery by starting the bundle containing the [https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.client.filediscovery/studentserviceEDEF.xml studentserviceEDEF.xml file] results in the creation of a StudentService proxy and it's injection into the StudentServiceClient.bindStudentService method.   Once injected the service instance methods are called, resulting in the following OSGi console output:
+
@GET
 +
@Produces(MediaType.APPLICATION_XML)
 +
@Path("/students/{studentId}")
 +
public Student getStudent(@PathParam("studentId") String id) {
 +
return students.get(id);
 +
}
  
<pre>
+
@POST
osgi> Discovered student service=com.mycorp.examples.student.StudentService.proxy@org.eclipse.ecf.remoteservice.RemoteServiceID[containerID=URIID [uri=http://localhost:8080/studentresource/rs];containerRelativeID=0]
+
@Produces(MediaType.APPLICATION_XML)
students=[Student [id=94381a53-b2b9-4cb4-ab36-1e8db4643f2f, name=Joe Senior, grade=First, address=Address [street=111 Park Ave, city=New York, state=NY, postalCode=11111]]]
+
@Path("/students/{studentName}")
Student 0=Student [id=94381a53-b2b9-4cb4-ab36-1e8db4643f2f, name=Joe Senior, grade=First, address=Address [street=111 Park Ave, city=New York, state=NY, postalCode=11111]]
+
public Student createStudent(@PathParam("studentName") String studentName) {
Student with id=94381a53-b2b9-4cb4-ab36-1e8db4643f2f=Student [id=94381a53-b2b9-4cb4-ab36-1e8db4643f2f, name=Joe Senior, grade=First, address=Address [street=111 Park Ave, city=New York, state=NY, postalCode=11111]]
+
if (studentName == null)
Created student=Student [id=391d2e3e-de8a-4dea-a9d6-26a408737eee, name=April Snow, grade=null, address=null]
+
return null;
Updated student=Student [id=391d2e3e-de8a-4dea-a9d6-26a408737eee, name=April Snow, grade=First, address=null]
+
synchronized (students) {
Deleted student=Student [id=391d2e3e-de8a-4dea-a9d6-26a408737eee, name=April Snow, grade=First, address=null]
+
Student s = new Student(studentName);
</pre>
+
s.setId(UUID.randomUUID().toString());
 +
students.put(s.getId(), s);
 +
return s;
 +
}
 +
}
  
==Summary==
+
@PUT
 +
@Consumes(MediaType.APPLICATION_XML)
 +
@Produces(MediaType.APPLICATION_XML)
 +
@Path("/students")
 +
public Student updateStudent(Student student) {
 +
Student result = null;
 +
if (student != null) {
 +
String id = student.getId();
 +
if (id != null) {
 +
synchronized (students) {
 +
result = students.get(student.getId());
 +
if (result != null) {
 +
String newName = student.getName();
 +
if (newName != null)
 +
result.setName(newName);
 +
result.setGrade(student.getGrade());
 +
result.setAddress(student.getAddress());
 +
}
 +
}
 +
}
 +
}
 +
return result;
 +
}
  
By using ECF's implementation of OSGi Remote Services and the [https://github.com/ECF/JaxRSProviders/tree/master/bundles ECF Jax-RS provider], it's easy to create dynamic OSGi Remote Services proxies to access the Jax-RS service.   The annotated interface (e.g. StudentService above) can easily be created from the server's Jax-RS resource class.  
+
@DELETE
 +
@Path("/students/{studentId}")
 +
@Produces(MediaType.APPLICATION_XML)
 +
public Student deleteStudent(@PathParam("studentId") String studentId) {
 +
return students.remove(studentId);
 +
}
 +
}
 +
</source>
 +
 
 +
[ https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.remoteservice.host/src/com/mycorp/examples/student/remoteservice/host/StudentServiceImpl.java Here is the full source code].  Note that Declarative Services is used to create and register the remote service.  This DS annotation
 +
 
 +
<source lang="java">
 +
@Component(immediate = true, properties = "rsexport.jersey.properties")
 +
public class StudentServiceImpl implements StudentService {
 +
...
 +
</source>
 +
 
 +
specifies that the service properties in rsexport.jersey.properties file are associated with this service.  Here is that properties file
 +
 
 +
<source lang="java">
 +
# Required OSGi Remote Service Property indicating that the StudentService should
 +
# be exported
 +
service.exported.interfaces=*
 +
# Optional OSGi Remote Service Property indicating that the ecf.jaxrs.jersey.server
 +
# distribution provider should be used to export this service
 +
service.exported.configs=ecf.jaxrs.jersey.server
 +
# Distribution-provider property defining the alias prefix for the Jersey server
 +
ecf.jaxrs.jersey.server.alias=/jersey/is/good
 +
# Other Jersey properties that could be defined
 +
ecf.jaxrs.jersey.server.urlContext=http://localhost:8080
 +
</source>
 +
 
 +
Since these properties include the OSGi-Remote Service standard properties (service.exported.interfaces and service.exported.config), when this component is registered by DS, the ECF Remote Service implementation recognizes that this service is to be exported, and it exports it with the given distribution provider (ecf.jaxrs.jersey.server) and with the given configuration.
 +
 
 +
==Summary==
  
It's also possible to create a custom distribution provider based upon alternative Jax-RS implementations, or customize the existing provider to meet security and other requirements.
+
By using ECF's implementation of OSGi Remote Services and the [https://github.com/ECF/JaxRSProviders/tree/master/bundles ECF Jax-RS provider], OSGi Remote Services proxy is automatically created to access the Jax-RS service.  Note that with Jax-RS compliant providers, the remote service can '''also''' be accessed via non-proxy clients...e.g. via curl, programmer created application code, etc.
  
 
==Background and Related Articles==
 
==Background and Related Articles==

Revision as of 12:47, 18 April 2016


Introduction

Jax RESTful Web Services (Jax-RS) is a popular standard/specification for exposing services for web-based remote access, with a number of implementations (e.g. Resteasy, Jersey, and CXF and more appearing all the time.

This tutorial will show the use of ECF's OSGi Remote Services to expose an arbitrary Jax-RS service as an OSGi Service and thereby gain the advantages of using OSGi Services, such as superior handling of service dynamics, service versioning, and a clear separation of service contract from service implementation.

ECF's implementation of OSGi Remote Services is unique because it provides open APIs for customizing or replacing distribution providers. Distribution providers are responsible for the actual transmission of remote service calls: marshalling the method arguments, unmarshalling return values, and transport of the call to and from the service host. ECF has a number of provider implementations, including a relatively new one based upon Jax-RS. The git repo containing this provider and the examples from this tutorial may be found here. This provider is based upon the Jax-RS specification, and uses the Jersey implementation from the Orbit project. This provider is small, modular, and extensible, allowing the easy customization or substitution of alternative Jax-RS implementations.

This tutorial will guide through

  1. Describing how Jax-RS services are defined
  2. Declaring an example Jax-RS remote service so that it may be used as an OSGi Remote Service
  3. Demonstrating how to use the Remote Service for accessing the underlying Jax-RS service

Jax-RS Services

With Jax-RS, typically a server-side implementation 'resource' class is annotated with Jax-RS specified java annotations. For example:

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
 
// The Java class will be hosted at the URI path "/helloworld"
@Path("/helloworld")
public class HelloWorldResource implements HelloWorldService {
 
    // The Java method will process HTTP GET requests
    @GET
    // The Java method will produce content identified by the MIME Media
    // type "text/plain"
    @Produces("text/plain")
    public String getMessage() {
        // Return some textual content
        return "Hello World";
    }
}

The @Path and @Get annotations are used by the Jax-RS implementation on the server to expose the sayHello method for access by a client via using an URL such as:

curl http://localhost:8080/helloworld

This would return "Hello There".

For this tutorial we will use a service for accessing a simple database of Students. Here is a Jax-RS implementation class. Notice the annotations on the public methods. These annotations signal to the Jax-RS implementation that these methods are to be exposed as remote services. When included in a webapp (war file) with the necessary Jax-RS implementation libraries and dependencies on (e.g.) a Tomcat server, this service would be exposed for web-based remote access.

In order to make this student database service available for clients/consumers as an OSGi Remote Service, it's useful to abstract an interface with the appropriate methods and including the same Jax-RS annotations. Here is such an interface for the StudentResource

@Path("/studentservice")
public interface StudentService {
 
	@GET
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students")
	List<Student> getStudents();
 
	@GET
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students/{studentId}")
	Student getStudent(@PathParam("studentId") String id);
 
	@POST
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students/{studentName}")
	Student createStudent(@PathParam("studentName") String studentName);
 
	@PUT
	@Consumes(MediaType.APPLICATION_XML)
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students")
	Student updateStudent(Student student);
 
	@DELETE
	@Path("/students/{studentId}")
	@Produces(MediaType.APPLICATION_XML)
	Student deleteStudent(@PathParam("studentId") String studentId);
}

Here is the complete interface.

Note: the method signatures and annotations are exactly the same as those given on the actual Jax-RS service resource class. The full example bundle project with this service interface and dependencies is provided here.

Note also that the annotations are only those standardized by the Jax-RS specification (i.e. in javax.ws.rs.* packages), and so are not bound to any Jax-RS implementation. This allows the use of a variety of JAX-RS implementations (Jersey, Apache CXF, RestEasy, others), without having to change the StudentService method signatures or annotations.

With this interface and the ECF Remote Service Jax-RS provider bundles and their dependencies, we can create a client/consumer application that uses this remote service. ECF's implementation will dynamically create a proxy implementation of the StudentService interface, and make it available to the consumer.

As an OSGi service, it can be discovered and made available in several ways, but the easiest is to have the proxy instance injected via Declarative Services. Here is the java code for a DS component that injects the StudentService into the client code:

	@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
	void bindStudentService(StudentService service) throws Exception {
		this.studentService = service;
		System.out.println("Discovered student service=" + this.studentService);
		// Get all students
		List<Student> students = studentService.getStudents();
		// Get first student from list
		Student s0 = students.get(0);
		// Print out first student
		System.out.println("Student0=" + s0);
		// If there is anyone there, then update
		if (s0 != null) {
			s0.setGrade("Eighth");
			// And update
			System.out.println("Updated Student0=" + studentService.updateStudent(s0));
		}
 
		// Create a new student
		Student newstudent = studentService.createStudent("April Snow");
		System.out.println("Created student=" + newstudent);
		// when done change the grade to first
		newstudent.setGrade("First");
		Address addr = new Address();
		addr.setStreet("111 NE 1st");
		addr.setCity("Austin");
		addr.setState("Oregon");
		addr.setPostalCode("97200");
		newstudent.setAddress(addr);
		// update
		Student updatednewstudent = studentService.updateStudent(newstudent);
		System.out.println("Updated student=" + updatednewstudent);
		// Then delete new student
		Student deletedstudent = studentService.deleteStudent(updatednewstudent.getId());
		System.out.println("Deleted student=" + deletedstudent);
	}

When called by the DS/SCR runtime, this code in the bindStudentService method invokes the StudentService proxy methods. At that time, these method calls will be turned into valid Jax-RS client calls by the ECR-created proxy.

Jax-RS Student Service Server

Here is the StudentService server code

// The jax-rs path annotation for this service
@Path("/studentservice")
// The OSGi DS (declarative services) component annotation. Note that the
// /rsexport.properties file defines this service impl as a remote service
// and configures the usage of the Jersey Jax-RS implementation as the
// desired distribution provider. See /rsexport.jersey.properties
@Component(immediate = true, properties = "rsexport.jersey.properties")
public class StudentServiceImpl implements StudentService {
 
...
	// Provide a map-based storage of students
	private static Map<String, Student> students = Collections.synchronizedMap(new HashMap<String, Student>());
	// Create a single student and add to students map
	static {
		Student s = new Student("Joe Senior");
		s.setId(UUID.randomUUID().toString());
		s.setGrade("First");
		Address a = new Address();
		a.setCity("New York");
		a.setState("NY");
		a.setPostalCode("11111");
		a.setStreet("111 Park Ave");
		s.setAddress(a);
		students.put(s.getId(), s);
	}
 
	// Implementation of StudentService based upon the students map
	@GET
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students")
	public List<Student> getStudents() {
		return new ArrayList<Student>(students.values());
	}
 
	@GET
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students/{studentId}")
	public Student getStudent(@PathParam("studentId") String id) {
		return students.get(id);
	}
 
	@POST
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students/{studentName}")
	public Student createStudent(@PathParam("studentName") String studentName) {
		if (studentName == null)
			return null;
		synchronized (students) {
			Student s = new Student(studentName);
			s.setId(UUID.randomUUID().toString());
			students.put(s.getId(), s);
			return s;
		}
	}
 
	@PUT
	@Consumes(MediaType.APPLICATION_XML)
	@Produces(MediaType.APPLICATION_XML)
	@Path("/students")
	public Student updateStudent(Student student) {
		Student result = null;
		if (student != null) {
			String id = student.getId();
			if (id != null) {
				synchronized (students) {
					result = students.get(student.getId());
					if (result != null) {
						String newName = student.getName();
						if (newName != null)
							result.setName(newName);
						result.setGrade(student.getGrade());
						result.setAddress(student.getAddress());
					}
				}
			}
		}
		return result;
	}
 
	@DELETE
	@Path("/students/{studentId}")
	@Produces(MediaType.APPLICATION_XML)
	public Student deleteStudent(@PathParam("studentId") String studentId) {
		return students.remove(studentId);
	}
}

[ https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.remoteservice.host/src/com/mycorp/examples/student/remoteservice/host/StudentServiceImpl.java Here is the full source code]. Note that Declarative Services is used to create and register the remote service. This DS annotation

@Component(immediate = true, properties = "rsexport.jersey.properties")
public class StudentServiceImpl implements StudentService {
...

specifies that the service properties in rsexport.jersey.properties file are associated with this service. Here is that properties file

# Required OSGi Remote Service Property indicating that the StudentService should
# be exported
service.exported.interfaces=*
# Optional OSGi Remote Service Property indicating that the ecf.jaxrs.jersey.server
# distribution provider should be used to export this service
service.exported.configs=ecf.jaxrs.jersey.server
# Distribution-provider property defining the alias prefix for the Jersey server
ecf.jaxrs.jersey.server.alias=/jersey/is/good
# Other Jersey properties that could be defined
ecf.jaxrs.jersey.server.urlContext=http://localhost:8080

Since these properties include the OSGi-Remote Service standard properties (service.exported.interfaces and service.exported.config), when this component is registered by DS, the ECF Remote Service implementation recognizes that this service is to be exported, and it exports it with the given distribution provider (ecf.jaxrs.jersey.server) and with the given configuration.

Summary

By using ECF's implementation of OSGi Remote Services and the ECF Jax-RS provider, OSGi Remote Services proxy is automatically created to access the Jax-RS service. Note that with Jax-RS compliant providers, the remote service can also be accessed via non-proxy clients...e.g. via curl, programmer created application code, etc.

Background and Related Articles

Tutorial: Using REST and OSGi Standards for Micro Services

Getting Started with ECF's OSGi Remote Services Implementation

OSGi Remote Services and ECF

Asynchronous Proxies for Remote Services

Static File-based Discovery of Remote Service Endpoints

Download ECF Remote Services/RSA Implementation

How to Add Remote Services/RSA to Your Target Platform

Back to the top