Using JAX-RS and Jersey to write RESTful services in OSGI (Apache Felix, Adobe CQ5/AEM)

Although using SlingSafeMethodsServlet or SlingAllMethodsServlet might work for some things to expose some JSON based Http services, it quickly runs into some limits when you want to do the equivalents of JAX-RS. These mainly have to do with writing resources which can be used to specify URLs according to patterns, using sub resources, not having to marshal/unmarshal request payloads and responses from JSON/XML to POJOs, etc. JAX-RS is a lot more feature rich and lets you program at a higher level.

However, how to get these JAX-RS resources you write to be automatically be picked up and serving requests? In Sling, the SlingMainMethodsServlet is the main servlet serving requests but we are still able to use the Felix Http Service implementation to serve requests outside of Sling.

The wonderful people at https://github.com/hstaudacher/osgi-jax-rs-connector have written a set of bundles which do the work of publishing your JAX-RS resources and providers. You don’t get all the full fledged features that you would installing this in a servlet container but you get most things to get running.

The first steps are installing these bundles:

1. com.eclipsesource.jaxrs : jersey-all : 2.10.1
2. com.eclipsesource.jaxrs : publisher : 4.1
3. com.eclipsesource.jaxrs : multipart : 2.0-SR1 (if you’re planning to upload files for consumption by the REST service)
4. com.fasterxml.jackson.jaxrs : jackson-jaxrs-base : 2.4.3 (for marshalling to JSON using jackson)
5. com.fasterxml.jackson.core : jackson-core : 2.4.3
6. com.fasterxml.jackson.core : jackson-databind : 2.4.3
7. com.fasterxml.jackson.core : jackson-annotations : 2.4.3
8. com.fasterxml.jackson.module : jackson-module-jaxb-annotations : 2.4.3
9. com.fasterxml.jackson.module : jackson-jaxrs-json-provider : 2.4.3
10. com.eclipsesource.jaxrs : provider-security : 2.0-SR1
11. com.fasterxml : classmate : 1.1.0
12. org.jboss.logging : jboss-logging : 3.1.4.GA

Check that all bundles start.

Once you’ve done this, you can write a JAX-RS Resource as follows:

@Service
@Component
@Path("/my-service")
@Produces({ MediaType.APPLICATION_JSON })
public class MyServiceImpl extends MyService {</code>

@GET
@Override
public EntityPOJO getEntity() {
....
}

}

Make sure that it is registered as a service or the jersey container does not pick up the service. If it is successful, your service will be available at


/services/my-service (default prefix is /services)

Now, suppose you want to secure these services using a security provider. You need to write your own security provider as followed in the example

You can easily write one which uses a CQ5 ResourceResolverFactory to validate a cookie or auth token and return a user principal and user roles which can then be used to secure the JAX-RS Resource Methods. Here is an example:

package test;

@Service
@Component
public class TokenBasedSecurityHandler implements AuthenticationHandler, AuthorizationHandler {

	@Reference
	private ResourceResolverFactory resourceResolverFactory;

	public boolean isUserInRole(Principal principal, String role) {
		// TODO read the implementation of principal and see if the role matches the group in cq
	
		return true;
	}

	@Override
	public Principal authenticate(ContainerRequestContext requestContext) {
		Cookie loginTokenCookie = requestContext.getCookies().get("login-token");
		if(loginTokenCookie != null) {
			String loginTokenCookieStr = loginTokenCookie.getValue();
			TokenCookie tokenCookie = TokenCookie.fromString(loginTokenCookieStr);
			if(tokenCookie!=null &amp;&amp; !tokenCookie.getInfos().isEmpty()) {
			TokenCookie.Info tokenInfo = tokenCookie.getInfos().values().iterator.next(); // you can //iterate through all of them if you like or use the first depending on your needs
			TokenCredentials tokenCredentials = new TokenCredentials(tokenInfo.token);
			AuthenticationInfo authInfo = AuthenticationInfo("TOKEN");
			info.put("user.jcr.credentials", tokenCredentials);
			ResourceResolver resourceResolver = null;
			PrincalUser principalUser = null;
			try {
			resourceResolver = resourceResolverFactory.getResourceResolver(authInfo);
			if(resourceResolver!=null) {
				User user = resourceResolver.adaptTo(User.class); //org.apache.jackrabbit.api.security.user.User
				Iterator groupIterator = user.memberOf();
			}	
			//TODO WRite an instance of Principal which captures your user and groups
			return principalUser;
			} catch (Exception e) { //TODO }
			
		}
	}


}

The code above is not complete but mostly there. Once you have this implementation you can use the following annotation above a Resource method to secure it.

@RolesAllowed({ "group-name"})

Happy hacking.

— Sarwar Bhuiyan

Advertisements
Using JAX-RS and Jersey to write RESTful services in OSGI (Apache Felix, Adobe CQ5/AEM)

16 thoughts on “Using JAX-RS and Jersey to write RESTful services in OSGI (Apache Felix, Adobe CQ5/AEM)

  1. Tobi says:

    Hello Sarwar,

    nice articel. How do i activate these 12 Bundles? I have added them as dependencies in my pom.xml but this isn’t enough i think?

    Hope you can help.

    Best,
    Tobi

    1. I can’t be sure which package might be missing but can you check error.log when you install the crx package. Usually I just put all the bundles in the install directory of the package and install that. The error log might say which package not being resolved. Also you can look at the stdout.log On Sat, 4 Apr 2015 at 18:27 Digital Projections wrote:

      >

      1. Tobi says:

        Ok than i have a problem with the Bundles, i think they are missing. I have only added the dependencies to my pom.xml file. Can you please tell me where i can download the compiled bundles or can you provide a download of your project?

      2. I believe I used a maven dependency plugin https://maven.apache.org/plugins/maven-dependency-plugin/copy-dependencies-mojo.html to copy those dependencies into the package build’s install folder for which the scope is not set to provided. For those dependencies I have mentioned above, don’t set a scope (or set to runtime or something) and have a look at the config and set the outputDirectory for those jars to ${project.build.directory}/jcr_root/apps//install. You can then inspect the zip package that is built to see if those dependencies were copied there.

        Hope this helps. I’ll see if I can dig out my pom and strip out the rest of the stuff

        Sarwar

        On Sun, Apr 5, 2015 at 12:05 PM, Digital Projections wrote:

        >

  2. I followed the instruction, trying to integrate JAX-RS application to Adobe CQ 6.0

    It worked OK, when my method respond with a String:

    @Service
    @Component
    @Path(“/my-service”)
    @Produces({ MediaType.APPLICATION_JSON })
    public class MyServiceImpl extends MyService {

    @GET
    @Override
    public String getEntity() {
    ….
    }

    }

    As soon as I added MyClass as the return type to method:

    @Service
    @Component
    @Path(“/my-service”)
    @Produces({ MediaType.APPLICATION_JSON })
    public class MyServiceImpl extends MyService {

    @GET
    @Override
    public MyClass getEntity() {
    ….
    return myClassObject;
    }

    }

    I got next error:
    Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=application/json, type=class com.myclass.MyClass, genericType=class com.myclass.MyClass.
    ….

    The fix is create the BundleActivator that registering the JacksonJsonProvider as a service in bundle, because the provider is not being registered automatically, so, the connector couldn’t reach it.

    package somepackage;

    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;

    public class Activator implements BundleActivator {

    @Override
    public void start(BundleContext context) throws Exception {
    context.registerService(“com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider”, new JacksonJsonProvider(), null);
    }

    @Override
    public void stop(BundleContext bundleContext) throws Exception {
    }
    }

  3. I thought that should already work. However, if you need to register a provider, it’s quite easy to do so with this framework. Create an OSGi service class which extends the JacksonJsonProvider and add the @Provider annotation to it.

  4. EsteBusta says:

    Hi Sarwar,

    Nice article, i have a question, when upload directly the bundles in my server the services works perfect, but when i added the bundles as dependencies in my bundle’s pom it just doesn’t work.

    I don’t know if i am understanding well, but when adding the dependencies in the POM, the goal will be that those dependencies will be displayed in the server? (i mean with the status and all the stuff)

    I read the previous comments , and you mentioned some of the “install directory of the package and install There”, can you give me some extra direction in how to install the bundles necesaries via maven ?
    It is possible?

  5. Sarwar Bhuiyan says:

    ESTEBUSTA, if you are running AEM, then your content package should have something like /apps/<your-project-name/install folder into which you can copy your jar files. If you're running sling, you might have a look at the maven-sling-plugin or use something like the File Install bundle and use a local install folder to deploy bundles to the server. Of course, you can always do it from Felix.

  6. Jax says:

    Hello Sarwar,
    thank you for this article. I have one question:
    “Make sure that it is registered as a service or the jersey container does not pick up the service”
    Do you mean the Service Component Registry so this happens with @Component, or do you refer to
    @Service. Can you please veriy that you refer to import org.jvnet.hk2.annotations.Service Annotation, which is located jersey-all.jar?
    Actually in the articles I reviewed here:
    http://eclipsesource.com/blogs/2014/02/04/step-by-step-how-to-bring-jax-rs-and-osgi-together/
    and here:
    https://wiki.eclipse.org/Tutorial:_Exposing_a_Jax_REST_service_as_an_OSGi_Remote_Service
    They did not mention to use @Service annotation at all?
    Is this sth. specific in the context of ADOBE CQ5/AEM which I do not use?

  7. Danqing Huang says:

    Thanks for sharing your knowledge.
    You said “/services/my-service (default prefix is /services)”, how can we change the this default prefix to a different name like: /webServices/my-service?

    1. If you go to the web console and look for a service with the pid com.eclipsesource.jaxrs.connector you will have the option of changing the prefix. Don’t change it to / though as that’ll conflict with sling.

      1. Amogh says:

        Hi Sarwar Bhuiyan,

        I could see the pid com.eclipsesource.jaxrs.connector in bundles and services, but could not find any config to change it. Could you please help ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s