Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

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

(Running a Server to Expose the Jax-RS Student Service)
(Jax-RS Remote Services)
 
(32 intermediate revisions by the same user not shown)
Line 74: Line 74:
 
==Introduction==
 
==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. [http://resteasy.jboss.org/ Resteasy], [https://jersey.java.net/ Jersey], and [http://cxf.apache.org/ CXF] and more appearing all the time.   
+
Jax RESTful Web Services (Jax-RS) is a popular standard for exposing rest services, with a number of implementations such as [https://jersey.java.net/ Jersey], [http://cxf.apache.org/ CXF]), with others appearing frequently.   
  
This tutorial will show how to use ECF's implementation of 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 in an unreliable networking environment, service versioning, and a clear separation of service contract from service implementation.
+
This tutorial will show the use of ECF's OSGi Remote Services to expose a Jax-RS service as an OSGi Remote Service and thereby gain all the advantages of 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 among the Remote Service/Remote Service Admin implementations because it provides open APIs for substituting '''distribution providers'''.  Distribution providers are responsible for the transmission of remote service calls:  marshalling the method arguments, unmarshalling return values, and transporting the call to and from the service using some network protocolECF has a number of provider implementations, including a relatively new one for Jax-RS.  The git repo containing this provider and the examples from this tutorial may be [https://github.com/ECF/JaxRSProviders found here].  This provider is based upon the [https://jcp.org/aboutJava/communityprocess/final/jsr339/index.html Jax-RS specification], and uses currently uses the [https://jersey.java.net Jersey implementation] from the Eclipse Foundation [http://www.eclipse.org/orbit Orbit project].  This provider is modular and extensible, allowing the easy replacement with other Jax-RS implementations.
+
ECF's uniquely provides open APIs for customizing or replacing the '''distribution providers''' responsible for providing the underlying remote service communication.   
  
This tutorial will guide the reader through
+
ECF has a number of distribution provider implementations, including two based upon Jax-RS. The git repo containing this provider and the examples from this tutorial may be [https://github.com/ECF/JaxRSProviders found here]. Two distribution providers are available based upon the [https://jcp.org/aboutJava/communityprocess/final/jsr339/index.html Jax-RS specification]. One uses the [https://jersey.github.io Jersey implementation] and the other uses the [http://cxf.apache.org/ Apache CXF implementation].  Both of these distribution providers support a small extensible core allowing the easy customization or substitution of alternative Jax-RS implementations.
  
#Describing how Jax-RS services are defined
+
This tutorial will guide through
#Declaring an example Jax-RS remote service so that it may be used as an OSGi Remote Service
+
#Demonstrating how to use the Remote Service for accessing the underlying Jax-RS service
+
  
==Jax-RS Services==
+
#Describing how Jax-RS remote services are implemented
 +
#Declaring an example Jax-RS remote CRUD service so that it may be used as an OSGi Remote Service
 +
#Showing how to implement the Remote Service
 +
 
 +
==Jax-RS Remote Services==
  
 
With Jax-RS, typically a server-side implementation 'resource' class is annotated with Jax-RS specified java annotations.  For example:
 
With Jax-RS, typically a server-side implementation 'resource' class is annotated with Jax-RS specified java annotations.  For example:
  
 
<source lang="java">
 
<source lang="java">
import javax.ws.rs.*;
+
// The Java class will be hosted at the URI path "/helloworld"
 
+
@Path("/helloworld")
@Path("/helloservice")
+
public class HelloWorldResource implements HelloWorldService {
public class HelloResource {
+
   
 
+
    // The Java method will process HTTP GET requests
 
     @GET
 
     @GET
     @Path("sayhello")
+
    // The Java method will produce content identified by the MIME Media
     public String sayHello() {
+
    // type "text/plain"
         return "Hello There";
+
     @Produces("text/plain")
 +
     public String getMessage() {
 +
        // Return some textual content
 +
         return "Hello World";
 
     }
 
     }
 
}
 
}
Line 107: Line 112:
  
 
<pre>
 
<pre>
curl http://localhost:8080/mycontext/helloservice/sayhello
+
curl http://localhost:8080/helloworld
 
</pre>
 
</pre>
  
This would return "Hello There" when accessed.
+
would return "Hello World".
  
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 describe a more complicated Jax-RS service for accessing a simple database of Students.   
  
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">
package com.mycorp.examples.student;
 
 
import java.util.List;
 
 
import javax.ws.rs.Consumes;
 
import javax.ws.rs.DELETE;
 
import javax.ws.rs.GET;
 
import javax.ws.rs.POST;
 
import javax.ws.rs.Path;
 
import javax.ws.rs.PathParam;
 
import javax.ws.rs.Produces;
 
import javax.ws.rs.core.MediaType;
 
 
 
@Path("/studentservice")
 
@Path("/studentservice")
 
public interface StudentService {
 
public interface StudentService {
  
 
@GET
 
@GET
@Produces(MediaType.APPLICATION_XML)
+
@Produces(MediaType.APPLICATION_JSON)
 
@Path("/students")
 
@Path("/students")
List<Student> getStudents();
+
Students getStudents();
  
 
@GET
 
@GET
@Produces(MediaType.APPLICATION_XML)
+
@Produces(MediaType.APPLICATION_JSON)
 +
@Path("/studentscf")
 +
CompletableFuture<Students> getStudentsCF();
 +
 
 +
@GET
 +
@Produces(MediaType.APPLICATION_JSON)
 
@Path("/students/{studentId}")
 
@Path("/students/{studentId}")
 
Student getStudent(@PathParam("studentId") String id);
 
Student getStudent(@PathParam("studentId") String id);
  
 
@POST
 
@POST
@Produces(MediaType.APPLICATION_XML)
+
@Produces(MediaType.APPLICATION_JSON)
@Path("/students/add/{studentName}")
+
@Path("/students/{studentName}")
Student addStudent(@PathParam("studentName") String studentName);
+
Student createStudent(@PathParam("studentName") String studentName);
  
@POST
+
@PUT
@Consumes(MediaType.APPLICATION_XML)
+
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_XML)
+
@Produces(MediaType.APPLICATION_JSON)
@Path("/students/update")
+
@Path("/students")
 
Student updateStudent(Student student);
 
Student updateStudent(Student student);
  
 
@DELETE
 
@DELETE
@Path("/students/delete/{studentId}")
+
@Path("/students/{studentId}")
@Produces(MediaType.APPLICATION_XML)
+
@Produces(MediaType.APPLICATION_JSON)
 
Student deleteStudent(@PathParam("studentId") String studentId);
 
Student deleteStudent(@PathParam("studentId") String studentId);
 
}
 
}
 
</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/src/com/mycorp/examples/student/StudentService.java Here is the complete interface class].
  
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 that the method signatures and Jax-rs annotations in this service interface are exactly the same as those given on the service resource implementation class
  
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.
+
<source lang="java">
 +
// The jax-rs path annotation for this service
 +
@Path("/studentservice")
 +
// The OSGi DS (declarative services) component annotation.
 +
@Component(immediate = true, property = { "service.exported.interfaces=*", "service.intents=osgi.async",
 +
"service.intents=jaxrs","osgi.basic.timeout=50000" })
 +
public class StudentServiceImpl implements 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 Declarative Services component that dynamically injects the 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);
 +
}
  
<source lang="java">
+
// Implementation of StudentService based upon the students map
package com.mycorp.examples.student.client;
+
@GET
 +
@Produces(MediaType.APPLICATION_JSON)
 +
@Path("/students")
 +
public Students getStudents() {
 +
Students result = new Students();
 +
result.setStudents(new ArrayList<Student>(students.values()));
 +
return result;
 +
}
  
import java.util.List;
+
@GET
 +
@Produces(MediaType.APPLICATION_JSON)
 +
@Path("/studentscf")
 +
public CompletableFuture<Students> getStudentsCF() {
 +
CompletableFuture<Students> cf = new CompletableFuture<Students>();
 +
cf.complete(getStudents());
 +
return cf;
 +
}
  
import com.mycorp.examples.student.Student;
+
@GET
import com.mycorp.examples.student.StudentService;
+
@Produces(MediaType.APPLICATION_JSON)
 +
@Path("/students/{studentId}")
 +
public Student getStudent(@PathParam("studentId") String id) {
 +
return students.get(id);
 +
}
  
public class StudentServiceClient {
+
@POST
 +
@Produces(MediaType.APPLICATION_JSON)
 +
@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;
 +
}
 +
}
  
        // Called by Service Component Runtime when a StudentService is discovered by RSA
+
@PUT
void bindStudentService(StudentService service) {
+
@Consumes(MediaType.APPLICATION_JSON)
System.out.println("Discovered student service=" + service);
+
@Produces(MediaType.APPLICATION_JSON)
// Get students
+
@Path("/students")
List<Student> originalStudents = service.getStudents();
+
public Student updateStudent(Student student) {
// Print list
+
Student result = null;
System.out.println("students=" + originalStudents);
+
if (student != null) {
// Get first student
+
String id = student.getId();
Student s = originalStudents.get(0);
+
if (id != null) {
System.out.println("Student 0=" + s);
+
synchronized (students) {
if (s != null) {
+
result = students.get(student.getId());
// Get this student via id
+
if (result != null) {
s = service.getStudent(s.getId());
+
String newName = student.getName();
System.out.println("Student with id=" + s.getId() + "=" + s);
+
if (newName != null)
 +
result.setName(newName);
 +
result.setGrade(student.getGrade());
 +
result.setAddress(student.getAddress());
 +
}
 +
}
 +
}
 
}
 
}
// Add a new student
+
return result;
Student newStudent = service.addStudent("April Snow");
+
}
System.out.println("Created student=" + newStudent);
+
 
// Update with grade
+
@DELETE
newStudent.setGrade("First");
+
@Path("/students/{studentId}")
newStudent = service.updateStudent(newStudent);
+
@Produces(MediaType.APPLICATION_JSON)
System.out.println("Updated student=" + newStudent);
+
public Student deleteStudent(@PathParam("studentId") String studentId) {
// Delete student
+
return students.remove(studentId);
System.out.println("Deleted student=" + service.deleteStudent(newStudent.getId()));
+
 
}
 
}
 
}
 
}
 
</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 proxy calls will be automatically turned into valid Jax-RS client calls by the constructed proxy.
+
[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 entire source for the Jax-RS service resource implementation class].
  
==Remote Service Discovery==
+
The full example project with this service interface and dependencies is [https://github.com/ECF/JaxRSProviders/tree/master/examples/com.mycorp.examples.student provided here] and the entire example is available in the three projects: '''com.mycorp.examples.student''', '''com.mycorp.examples.student.remoteservice.host''', and '''com.mycorp.examples.student.client''' in [https://github.com/ECF/JaxRSProviders/tree/master/examples this repo location]. 
  
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 ServiceSee [[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.
+
Note that the code in these projects only include dependencies on standardized Jax-RS annotation classes onlyNote also that the following defines the OSGi Remote Service standard properties needed to export using either the Jersey or CXF Jax-RS implementation
  
==Jax-RS Student Service Server==
+
<source lang="java">
 +
// The OSGi DS (declarative services) component annotation.
 +
@Component(immediate = true, property = { "service.exported.interfaces=*", "service.intents=osgi.async",
 +
"service.intents=jaxrs","osgi.basic.timeout=50000" })
 +
</source>
  
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 on the client.
+
They do not contain dependencies on either Jax-RS implementation (Jersey, CXF, etc) code; nor do they contain dependencies on either ECF Remote Services implementation code or OSGi code.
  
==Running the Jax-RS OSGi Service Client==
+
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.  With the ECF distribution provider, this allows the use of either Jersey or Apache CXF '''without''' having to change the StudentService method signatures or annotations.  Further, if one wishes, the Jax-RS distribution provider can be extended to support a Jax-RS implementation other than Jersey or Apache CXF.
  
Using the StudentServiceClient class as above, and triggering the RSA discovery via starting the bundle containing the EDEF file given above, results in the creation of a StudentService proxy, and it's injection via DS into the StudentServiceClient.bindStudentService method, resulting in the following OSGi console output:
+
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.
  
<pre>
+
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 proxy into the service consumer code:
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]
+
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]]]
+
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]]
+
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]]
+
Created student=Student [id=391d2e3e-de8a-4dea-a9d6-26a408737eee, name=April Snow, grade=null, address=null]
+
Updated student=Student [id=391d2e3e-de8a-4dea-a9d6-26a408737eee, name=April Snow, grade=First, address=null]
+
Deleted student=Student [id=391d2e3e-de8a-4dea-a9d6-26a408737eee, name=April Snow, grade=First, address=null]
+
</pre>
+
  
One major advantage of this approach is that
+
<source lang="java">
 +
@Reference
 +
void bindStudentService(StudentService service) throws Exception {
 +
this.studentService = service;
 +
System.out.println("Discovered student service=" + this.studentService);
 +
// Get all students
 +
Students students = studentService.getStudents();
 +
// Get first student from list
 +
Student s0 = students.getStudents().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));
 +
}
 +
 
 +
// Get student with completablefuture
 +
studentService.getStudentsCF().whenComplete((s, except) -> {
 +
if (except != null)
 +
except.printStackTrace();
 +
else
 +
System.out.println("Student=0=" + s.getStudents().get(0));
 +
});
 +
// 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);
 +
}
 +
</source>
 +
 
 +
[https://github.com/ECF/JaxRSProviders/blob/master/examples/com.mycorp.examples.student.client/src/com/mycorp/examples/student/client/StudentServiceClient.java Here is the entire source for the consumer implementation class]. 
 +
 
 +
When the StudentService remote service is discovered and the proxy created, the proxy is injected by calling the DS/SCR runtime.  The example code in the bindStudentService method invokes the StudentService proxy methods.  These method calls will be turned into valid Jax-RS http client calls by the ECF-created remote service proxy.
 +
 
 +
==Summary==
 +
 
 +
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 discovered, validated, and created to allow synchronous and/or asynchronous access to the Jax-RS service.  Note that if desired, the remote service can also be easily accessed via non-proxy or non-Java clients...e.g. via curl, application code, javascript, other languages, etc.
  
 
==Background and Related Articles==
 
==Background and Related Articles==
 +
 +
[[Tutorial: Using REST and OSGi Standards for Micro Services]]
  
 
[[Getting Started with ECF's OSGi Remote Services Implementation]]
 
[[Getting Started with ECF's OSGi Remote Services Implementation]]

Latest revision as of 19:54, 15 October 2018


Introduction

Jax RESTful Web Services (Jax-RS) is a popular standard for exposing rest services, with a number of implementations such as Jersey, CXF), with others appearing frequently.

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

ECF's uniquely provides open APIs for customizing or replacing the distribution providers responsible for providing the underlying remote service communication.

ECF has a number of distribution provider implementations, including two based upon Jax-RS. The git repo containing this provider and the examples from this tutorial may be found here. Two distribution providers are available based upon the Jax-RS specification. One uses the Jersey implementation and the other uses the Apache CXF implementation. Both of these distribution providers support a small extensible core allowing the easy customization or substitution of alternative Jax-RS implementations.

This tutorial will guide through

  1. Describing how Jax-RS remote services are implemented
  2. Declaring an example Jax-RS remote CRUD service so that it may be used as an OSGi Remote Service
  3. Showing how to implement the Remote Service

Jax-RS Remote Services

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

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

would return "Hello World".

For this tutorial we will describe a more complicated Jax-RS service for accessing a simple database of Students.

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_JSON)
	@Path("/students")
	Students getStudents();
 
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/studentscf")
	CompletableFuture<Students> getStudentsCF();
 
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/students/{studentId}")
	Student getStudent(@PathParam("studentId") String id);
 
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/students/{studentName}")
	Student createStudent(@PathParam("studentName") String studentName);
 
	@PUT
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/students")
	Student updateStudent(Student student);
 
	@DELETE
	@Path("/students/{studentId}")
	@Produces(MediaType.APPLICATION_JSON)
	Student deleteStudent(@PathParam("studentId") String studentId);
}

Here is the complete interface class.

Note that the method signatures and Jax-rs annotations in this service interface are exactly the same as those given on the service resource implementation class

// The jax-rs path annotation for this service
@Path("/studentservice")
// The OSGi DS (declarative services) component annotation. 
@Component(immediate = true, property = { "service.exported.interfaces=*", "service.intents=osgi.async",
		"service.intents=jaxrs","osgi.basic.timeout=50000" })
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_JSON)
	@Path("/students")
	public Students getStudents() {
		Students result = new Students();
		result.setStudents(new ArrayList<Student>(students.values()));
		return result;
	}
 
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/studentscf")
	public CompletableFuture<Students> getStudentsCF() {
		CompletableFuture<Students> cf = new CompletableFuture<Students>();
		cf.complete(getStudents());
		return cf;
	}
 
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/students/{studentId}")
	public Student getStudent(@PathParam("studentId") String id) {
		return students.get(id);
	}
 
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	@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_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	@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_JSON)
	public Student deleteStudent(@PathParam("studentId") String studentId) {
		return students.remove(studentId);
	}
}

Here is the entire source for the Jax-RS service resource implementation class.

The full example project with this service interface and dependencies is provided here and the entire example is available in the three projects: com.mycorp.examples.student, com.mycorp.examples.student.remoteservice.host, and com.mycorp.examples.student.client in this repo location.

Note that the code in these projects only include dependencies on standardized Jax-RS annotation classes only. Note also that the following defines the OSGi Remote Service standard properties needed to export using either the Jersey or CXF Jax-RS implementation

// The OSGi DS (declarative services) component annotation. 
@Component(immediate = true, property = { "service.exported.interfaces=*", "service.intents=osgi.async",
		"service.intents=jaxrs","osgi.basic.timeout=50000" })

They do not contain dependencies on either Jax-RS implementation (Jersey, CXF, etc) code; nor do they contain dependencies on either ECF Remote Services implementation code or OSGi code.

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. With the ECF distribution provider, this allows the use of either Jersey or Apache CXF without having to change the StudentService method signatures or annotations. Further, if one wishes, the Jax-RS distribution provider can be extended to support a Jax-RS implementation other than Jersey or Apache CXF.

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 proxy into the service consumer code:

	@Reference
	void bindStudentService(StudentService service) throws Exception {
		this.studentService = service;
		System.out.println("Discovered student service=" + this.studentService);
		// Get all students
		Students students = studentService.getStudents();
		// Get first student from list
		Student s0 = students.getStudents().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));
		}
 
		// Get student with completablefuture
		studentService.getStudentsCF().whenComplete((s, except) -> {
			if (except != null)
				except.printStackTrace();
			else
				System.out.println("Student=0=" + s.getStudents().get(0));
		});
		// 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);
	}

Here is the entire source for the consumer implementation class.

When the StudentService remote service is discovered and the proxy created, the proxy is injected by calling the DS/SCR runtime. The example code in the bindStudentService method invokes the StudentService proxy methods. These method calls will be turned into valid Jax-RS http client calls by the ECF-created remote service proxy.

Summary

By using ECF's implementation of OSGi Remote Services and the ECF Jax-RS provider, OSGi Remote Services proxy is automatically discovered, validated, and created to allow synchronous and/or asynchronous access to the Jax-RS service. Note that if desired, the remote service can also be easily accessed via non-proxy or non-Java clients...e.g. via curl, application code, javascript, other languages, 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

Copyright © Eclipse Foundation, Inc. All Rights Reserved.