Pages

Thursday, January 10, 2013

How to write a custom SlingServlet


The Problem(s)

You want to write a custom SlingServlet to render a HTML page, or generate JSON to be consumed by a front end such as a mobile device.

You also want the OSGI bundle to have its settings editable in the Felix Console.

The Solution

So you want to make a configurable OSGI bundle to allow various items to be changed in the Felix Console?

Let's take look at a servlet that reads some settings from the internal config, and replaces any text from a webpage and re-renders the text in the client.

Note, if you are getting code complete issues due to some packages not being recognized by CRXDE, then take a look here to see if it solves your problem.

Step 1 - Create a bundle

Bundles are a set of files with a manifest that CQ5 can install.

Create standard nt:folder to represent your application. In this case, we'll call it SlingServlet.

1. Open up CRXDE Lite. 
2. Right click on the apps folder, click on create folder, and call it SlingServletExample.
3. VERY IMPORTANT: Right click on the SlingServletExample folder and create a folder called install. Note, the install folder should be /apps/SlingServletExample/install. Failure to do so will mean your bundle will not automatically be deployed. See the gotchya below for full details
4. Right click on the SlingServletExample folder, click on new->Create Bundle. Fill in the Symbolic Name and Name to be WebpageTransformer. Type "Transforms webpages" into the description, and give it a package name of org.example.slingservlet.transformer. Click OK.
Congratulations. You've created a bundle. 

BEFORE YOU MOVE ON: 

Bit of a gotchya here. If you look at the WebpageTransformer.bnd package, you'll see the following two lines:
# Export-Package: *
# Import-Package: *

This is a bit of a nasty one, as to access this bundle from the outside world, you'll need to remove the # from both lines, or else all packages will remain as private only.

For full details, see the gotchya link below.

Step 2 - Create the Transformer Sling Servlet

Right click on the transformer folder, and click New -> Class

Name it TransformerClass, and set superclass to be org.apache.sling.api.servlets.SlingSafeMethodsServlet

Click on Finish.

This example takes a webpage, and transforms a supplied set of text into another set of text.

Here is the full TransformerClass.java contents.

TransformerClass.java

package org.example.slingservlet.transformer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.rmi.ServerException;
import java.util.Dictionary;

import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.osgi.service.component.ComponentContext;

@SlingServlet(paths="/bin/mySearchServlet", methods = "GET", metatype=true)
@Properties({
@org.apache.felix.scr.annotations.Property(name="PAGE-PROVIDER", description="Page Provider Address", value="http://www.google.com"),
@org.apache.felix.scr.annotations.Property(name="SOURCE-TEXT", description="Source Text to Replace", value="Search"),
@org.apache.felix.scr.annotations.Property(name="TARGET-TEXT", description="Target Replacement Text", value="Find of Awesomeness")
})
public class TransformerClass extends SlingSafeMethodsServlet {
private static final long serialVersionUID = 2598426539166789515L;

public static final String PAGE_PROVIDER="PAGE-PROVIDER";
public static final String SOURCE_TEXT="SOURCE-TEXT";
public static final String TARGET_TEXT="TARGET-TEXT";
private String pageProvider;
private String sourceText;
private String targetText;
@SuppressWarnings("unused")
@Reference
private SlingRepository repository;
protected void activate(ComponentContext componentContext){
configure(componentContext.getProperties());
}
protected void configure(Dictionary<?, ?> properties) {
this.pageProvider=OsgiUtil.toString(properties.get(PAGE_PROVIDER), null);
this.sourceText=OsgiUtil.toString(properties.get(SOURCE_TEXT), null);
this.targetText=OsgiUtil.toString(properties.get(TARGET_TEXT), null);
}
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServerException, IOException {
URL serverAddress=null;
BufferedReader reader=null;
HttpURLConnection internalHttpRequest=null;
PrintWriter responseWriter = response.getWriter();
try
{
serverAddress=new URL(pageProvider);
internalHttpRequest = (HttpURLConnection)serverAddress.openConnection();
InputStream in=null;
try
{
in = internalHttpRequest.getInputStream();
}
catch(Exception e)
{
responseWriter.append(e.toString());
}
if(in!=null)
{
reader = new BufferedReader(new InputStreamReader(in));
}
}
finally
{
if(reader!=null)
{
String line=null;
while((line=reader.readLine()) != null)
{
responseWriter.append(line.replace(sourceText, targetText) + "\n");
}
}
responseWriter.flush();
if(internalHttpRequest!=null)
{
internalHttpRequest.disconnect();
}
}
}
}

Servlet Address
@SlingServlet(paths="/bin/mySearchServlet", methods = "GET", metatype=true)

CQ5 utilises Java Annotations to configure classes.

Here, we are setting the SlingServlet annotation to have 3 values:
paths, methods and metatype.
The paths variable allows you to set the url that will resolve to the slingservlet.

In our case, if you access http://localhost:4502/bin/mySearchServlet, you will be served the servlet, rather than whatever may reside there in the JCR.

Methods specifies a GET method

The metatype option being set to true, instructs CQ5 to provide a config page in the Felix console.

Servlet Properties

The @Properties annotation contains multiple @Property annotations contained in our Sling Servlet.

It is worth noting that if you are using javax.jcr.Property to get a property from a JCR Node, you will have a common namespace conflict with the org.apache.felix.scr.annotations.Property.

I personally think that it's best to use org.apache.felix.scr.annotations.Property as the inline declaration, rather than javax.jcr.Property, as the Property is something that is generally still readable in long format, and won't influence the legibility of your code.


@SlingServlet(paths="/bin/mySearchServlet", methods = "GET", metatype=true)
@Properties({
@org.apache.felix.scr.annotations.Property(name="PAGE-PROVIDER", description="Page Provider Address", value="http://www.google.com"),
@org.apache.felix.scr.annotations.Property(name="SOURCE-TEXT", description="Source Text to Replace", value="Search"),
@org.apache.felix.scr.annotations.Property(name="TARGET-TEXT", description="Target Replacement Text", value="Find of Awesomeness")
})


Here you can see that the individual @Property has a number of values that will be visible via the OSGI console:


Property NamePurpose
nameThe name of the property used as the key to retrieve it later in the configure function
descriptionThe text that is used to describe the property in the OSGi property editor
valueThe default value that should be used for the property in the OSGi console.
*It is worth noting that if you add a subsequent property to a previously configured or saved OSGi console, the default value will not be used. Also, if the default value is changed in code, the default settings will need ot be manually invoked during a deploy, even if the package has been reinstalled.

Each servlet Class is instantiated as a stateless singleton when CQ5 is started, save for any global configuration values. These values need to be loaded from the config when the servlet is activated in the following function:


protected void activate(ComponentContext componentContext){
configure(componentContext.getProperties());
}

The activate function is called via the SlingServlet interface (If you know the specific interface, please comment below, short on time to find it right now.) when the Bundle is activated.

The activate function calls our own configure function, which gets a property dictionary.


protected void configure(Dictionary<?, ?> properties) {
this.pageProvider=OsgiUtil.toString(properties.get(PAGE_PROVIDER), null);
this.sourceText=OsgiUtil.toString(properties.get(SOURCE_TEXT), null);
this.targetText=OsgiUtil.toString(properties.get(TARGET_TEXT), null);
}

Here, we set our private variables with the OsgiUtil.toString function. Note that this class has been deprecated, but as of CQ5.5, I am not aware of a cleaner way of doing this. If you know of one, please comment below.

The final thing you need to know is that you must override the following function:

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServerException, IOException {

Note, that you have access to both the request and response, and can do whatever you need it to do.

I shall leave it to the reader to go through my code for the page transformation.



Step 3 - Build the servlet

The first thing to make sure of is that your install folder is in the right place. (read Step 1-3 very carefully. IF YOU DO NOT DO THIS, YOUR COMPONENT WILL NOT AUTOMATICALLY INSTALL DURING BUILD).

Double click on the WebpageTransformer.bnd file, and increment the Bundle Version number. If you do not increment the number, CQ5 may get confused and not automatically deploy your bundle.

When you are happy, right click on the ProviderAssets.bnd file, navigate to the Build sub menu, and click on Build Bundle.

If everything goes well, your bundle should be built and deployed.

Step 4 - Configuring your bundle

Navigate to your felix OSGi console.

http://localhost:4502/system/console/configMgr

Find your WebpageTransformer Bundle in this list. To the right, you should see a pencil icon. If you don't, check your metadata is correct, and contains metatype=true.
@SlingServlet(paths="/bin/mySearchServlet", methods = "GET", metatype=true)

take note of the settings, and make sure that they are default.

Navigate to http://localhost:4502/bin/mySearchServlet

If nothing comes up, you may need to change Apache Sling Servlet/Script Resolver and Error Handler config on this page.

Search for it, and click the pencil to edit your config, and look at your execution paths. You may need to add /bin/ to your execution paths if any are specified.

Once you can see the page, notice that it displays the google page, except the Search button displays Find of Awesomeness as the button label.

You can play with the settings, update them, and see the changes on the servlet page.

Discussion

Sling Servlets are a very handy tool to convert anything in your JCR to JSON, to aggregate and expose data sources to other components and applications in your ecosystem, or to provide an endpoint for the wild for any information you need to supply.

You can integrate the servlet with your security engine, and generally do whatever needs to be done.

If you want to access the JCR repository from a sling servlet, this tutorial may possibly be of assistance to you.

14 comments:

  1. Thanks for this post.

    Another way to get hold of the config properties is via the PropertiesUtil which is under the same package org.apache.sling.commons.osgi, but under the the osgi bundle 'Apache Sling Security'

    ReplyDelete
  2. Can we make a servlet class as final???
    for more details :http://www.itsoftpoint.com/?page_id=2822

    ReplyDelete
  3. My servlet is not accessible on author instance without authentication.
    Any way to bypass that?

    ReplyDelete
  4. Of all the way essay locations you retain shared besides reviewed is there unique that you can suggest to smartcustomwriting? I am immobile taxing to scope the opinion after observance article writing besides till I do I am scared I desire retain to observe sharing these liturgys.

    ReplyDelete
  5. I am using AEM 6.1. I've created this example in CRXDE Lite but the code does not compile. Errors:
    SlingServlet cannot be resolved to a type.
    Properties cannot be resolved to a type

    Any idea? please

    ReplyDelete
  6. The quality can be tested with time. Our custom writing service has many years of experience and thousands of satisfied clients. Become one of them and order a high-quality paper with us!
    essay writing company

    ReplyDelete
  7. I have never heard about this servlet application. Thank you very much for sharing. I like getting to know new things and I would want you to update if anything. Improve Your Life With The Classical Music Essay just in case you need a paper to write on. The sample is excellent let alone all the other samples provided by the blog of the service.

    ReplyDelete
  8. Hey there guys! Really glad to see you all here! Thanks a lot for the shares, awesome services! Guys, I'm really interested in using those, so I've found this writemyessaytoday review here. What do you think about it? Thanks a lot!

    ReplyDelete
  9. It will not be a easy task for you. Here you need deep knowledge about the topic. You will find some difficult in this. Custom Essay Writing Services can help you to solve this.

    ReplyDelete
  10. Thank nyou for such a detailed explanation! I've been looking for this information for a very long time! The Java class is the one programming language that is not very simple to cope with. That's why when I have some troubles with a homework writing devoted to this topic, I examine this issue very carefully!

    ReplyDelete
  11. Can you share some examples where front end developers get the json response... I have to forward this information to custom writing services.

    ReplyDelete
  12. Thanks for detailed instructions. Despite managing http://musicforwriters.com I am interested in computer technologies, especially SlingServlet, HTML and JSON.

    ReplyDelete
  13. Thanks for instructions. I'll try write a custom SlingServlet. Cuz my previous writing process was failed. So thanks a lot:)

    ReplyDelete