4
votes

We are trying to provide a clean URI structure for external endpoints to pull json information from CQ5.

For example, if you want to fetch information about a particular users history (assuming you have permissions etc), ideally we would like the endpoint to be able to do the following:

/bin/api/user/abc123/phone/555-klondike-5/history.json

In the URI, we would specifying /bin/api/user/{username}/phone/{phoneNumber}/history.json so that it is very easy to leverage the dispatcher to invalidate caching changes etc without invalidating a broad swath of cached information.

We would like to use a sling servlet to handle the request, however, I am not aware as to how to put variables into the path.

It would be great if there were something like @PathParam from JaxRS to add to the sling path variable, but I suspect it's not available.

The other approach we had in mind was to use a selector to recognise when we are accessing the api, and thus could return whatever we wanted to from the path, but it would necessitate a singular sling servlet to handle all of the requests, and so I am not happy about the approach as it glues a lot of unrelated code together.

Any help with this would be appreciated.


UPDATE:

If we were to use a OptingServlet, then put some logic inside the accepts function, we could stack a series of sling servlets on and make the acceptance decisions from the path with a regex.

Then during execution, the path itself can be parsed for the variables.

5

5 Answers

5
votes

If the data that you provide comes from the JCR repository, the best is to structure it exactly as you want the URLs to be, that's the recommended way of doing things with Sling.

If the data is external you can create a custom Sling ResourceProvider that you mount on the /bin/api/user path and acquires or generates the corresponding data based on the rest of the path.

The Sling test suite's PlanetsResourceProvider is a simple example of that, see http://svn.apache.org/repos/asf/sling/trunk/launchpad/test-services/src/main/java/org/apache/sling/launchpad/testservices/resourceprovider/

The Sling resources docs at https://sling.apache.org/documentation/the-sling-engine/resources.html document the general resource resolution mechanism.

2
votes

It is now possible to integrate jersy(JAX-RS) with CQ. We are able to create primitive prototype to say "Hello" to the world.

https://github.com/hstaudacher/osgi-jax-rs-connector

With this we can use the @PathParam to map the requests

Thanks and Regards, San

1
votes

There is no direct way to create such dynamic paths. You could register servlet under /bin/api/user.json and provide the rest of the path as a suffix:

/bin/api/user.json/abc123/phone/555-klondike-5/history
^                 ^
|                 |
servlet path      suffix starts here

then you could parse the suffix manually:

@SlingServlet(paths = "/bin/api/user", extensions = "json")
public class UserServlet extends SlingSafeMethodsServlet {
    public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
        String suffix = request.getRequestPathInfo().getSuffix();
        String[] split = StringUtils.split(suffix, '/');
        // parse split path and check if the path is valid
        // if path is not valid, send 404:
        // response.sendError(HttpURLConnection.HTTP_NOT_FOUND);
    }
}
0
votes

The RESTful way to approach this would be to have the information stored in the structure that you want to use. i.e. /content/user/abc123/phone/555-klondike-5/history/ would contain all the history nodes for that path.

In that usage. you can obtain an out of the box json response by simply calling

/content/user/abc123/phone/555-klondike-5/history.json

Or if you need something in a specific json format you could use the sling resource resolution to use a custom json response.

0
votes

Excited to share this! I've worked ~ a week solving this, finally have the best Answer.

First: Try to use Jersey

The osgi-jax-rs-connector suggested by kallada is best, but I couldn't get it working on Sling 8. I lost a full day trying, all I have to show for it are spooky class not found errors and dependency issues.

Solution: The ResourceProvider

Bertrand's link is for Sling 9 only, which isn't released. So here's how you do it in Sling 8 and older!

Two Files:

  • ResourceProvider
  • Servlet

The ResourceProvider

The purpose of this is only to listen to all requests at /service and then produce a "Resource" at that virtual path, which doesn't actually exist in the JCR.

@Component
@Service(value=ResourceProvider.class)
@Properties({
        @Property(name = ResourceProvider.ROOTS, value = "service/image"),
        @Property(name = ResourceProvider.OWNS_ROOTS, value = "true")
})
public class ImageResourceProvider implements ResourceProvider  {

@Override
public Resource getResource(ResourceResolver resourceResolver, String path) {

    AbstractResource abstractResource;
    abstractResource = new AbstractResource() {
        @Override
        public String getResourceType() {
            return TypeServlet.RESOURCE_TYPE;
        }

        @Override
        public String getResourceSuperType() {
            return null;
        }

        @Override
        public String getPath() {
            return path;
        }

        @Override
        public ResourceResolver getResourceResolver() {
            return resourceResolver;
        }

        @Override
        public ResourceMetadata getResourceMetadata() {
            return new ResourceMetadata();
        }
    };

    return abstractResource;
}

@Override
public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest httpServletRequest, String path) {
    return getResource(resourceResolver , path);
}

@Override
public Iterator<Resource> listChildren(Resource resource) {
    return null;
}
}

The Servlet

Now you just write a servlet which handles any of the resources coming from that path - but this is accomplished by handling any resources with the resource type which is produced by the ResourceProvider listening at that path.

@SlingServlet(
        resourceTypes = TypeServlet.RESOURCE_TYPE,
        methods = {"GET" , "POST"})
public class TypeServlet extends SlingAllMethodsServlet {


    static final String RESOURCE_TYPE = "mycompany/components/service/myservice";

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

        final String [] pathParts = request.getResource().getPath().split("/");
        final String id = pathParts[pathParts.length-1];
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        try {
            out.print("<html><body>Hello, received this id: " + id + "</body></html>");
        } finally {
             out.close();
        }
    }
}

Obviously your servlet would do something much more clever, such as process the "path" String more intelligently and probably produce JSON.