Wednesday, September 3, 2008

Sling OSGI Track pt 6: Sling Servlets I

This is the continuation of

In this part, I will show how to create your own Sling Servlet and deploy it as an OSGi Bundle.

As you may have seen from the sling documentation, there is a pretty straightforward way to create scripts on the search paths (/apps, /libs) that will be executed depending on properties of the requested resource (most notably sling:resourceType.

You can do the same using servlets, if you prefer.
Using servlets, you can also do a lot more than with scripts, and we'll have a first look at what can be done right now.
As a first easy example, I will show how to create a servlet that uses the service created in the previous parts and makes the output available at /system/osgitest/info .

Aims:

  • Show how to create a Sling Servlet and register it to respond to a preset path.

Ingredients:

Files:

Outline:

  • Project structure
  • The servlet implementation
  • The pom: nothing special
  • Maven dependencies: oops
  • Build, Deploy, Test

Execution:

Project structure

The project structure is simpler than anything we had so far. We actually need no more than the servlet implementation and the pom. Let's have a look:

.
|-- pom.xml
`-- src
    `-- main
        `-- java
            `-- mh
                `-- osgitest
                    `-- client
                        `-- OsgiTestClientServlet.java
The servlet implementation

Let's have a look at the servlet implementation:

  • The servlet extends SlingSafeMethodsServlet (l. 25).
    This is a sling-provided class that can be extended to implement servlets that do not have side-effects, i.e. do not modify data. See http://svn.apache.org/viewvc/incubator/sling/trunk/api/src/main/java/org/apache/sling/api/servlets/SlingSafeMethodsServlet.java?view=markup for the details.
  • The servlet overrides the doGet method (l. 31ff).
    In this method, it checks whether it's "service" variable is set (which is of the type 'SampleService' from our previous parts of this track), and if so calls sayHello() on the object to write the result to the output stream.
  • OK, so how can the service variable ever be anything but null? That happens through the src.reference directive in line 27. This will create an OSGi service descriptor, and the OSGi container will inject the reference to a SampleService instance, if available.
  • In line 22, the sling.servlet.paths property is set to /system/osgitest/info. This creates the mapping to the URL /system/osgitest/info, which the servlet will respond to.
    There are many other options of creating mappings depending on the resource that is being accessed, depending on method, etc. Part of this is explained in http://incubator.apache.org/sling/site/servlet-resolution.html, but that description is outdated. I'll look into the other options in further posts.
package mh.osgitest.client;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import mh.osgitest.SampleService;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;

/**
 * A Servlet that uses the service provided in the mh.osgitest to retrieve the
 * phrase "hello".
 * 
 * @scr.component immediate="true" metatype="no"
 * @scr.service interface="javax.servlet.Servlet"
 * @scr.property name="service.description" value="Sample OSGi Client Servlet"
 * @scr.property name="service.vendor" value="Moritz Havelock"
 * @scr.property name="sling.servlet.paths" value="/system/osgitest/info"
 */
@SuppressWarnings("serial")
public class OsgiTestClientServlet extends SlingSafeMethodsServlet {
	
	/** @scr.reference */
	private final SampleService service = null;

	@Override
	protected void doGet(SlingHttpServletRequest request,
			SlingHttpServletResponse response) throws ServletException,
			IOException {
		if (service == null) {
			response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
					"no reference to service available");
		} else {
			response.setContentType("text/plain");
			response.getOutputStream().print(service.sayHello());
		}
	}
}
The pom: nothing special

The pom looks quite familiar. What's different this time?
On lines 93-97, we have a dependency on the org.apache.sling.api artifact. That is because we are using sling specific types in the servlet implementation. I have here used the version 2.0.0.incubator-SNAPSHOT, which is what comes with CRX Cup edition.
On lines 98-102, we depend on the mh.studies.sling.osgitest artifact. This should be in your m2 repository from the previous tracks.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>mh.studies</groupId>
   <artifactId>mh.studies.sling.osgitest.client</artifactId>
   <packaging>bundle</packaging>
   <name>A Servlet Client for the OSGi Test Bundle</name>
   <version>0.0.1</version>
   <description />
   <build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
               <source>1.5</source>
               <target>1.5</target>
            </configuration>
         </plugin>
         <plugin>
            <groupId>org.apache.sling</groupId>
            <artifactId>maven-sling-plugin</artifactId>
            <version>2.0.2-incubator</version>
            <executions>
               <execution>
                  <id>install-bundle</id>
                  <goals>
                     <goal>install</goal>
                  </goals>
                  <configuration>
                     <slingUrl>http://localhost:7402/system/console/install</slingUrl>
                     <user>admin</user>
                     <password>admin</password>
                  </configuration>
               </execution>
            </executions>
         </plugin>
         <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-scr-plugin</artifactId>
            <version>1.0.7</version>
            <executions>
               <execution>
                  <id>generate-scr-scrdescriptor</id>
                  <goals>
                     <goal>scr</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
         <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-bundle-plugin</artifactId>
            <version>1.4.3</version>
            <extensions>true</extensions>
            <configuration>
               <instructions>
                  <Export-Package></Export-Package>
                  <Private-Package>mh.osgitest.client</Private-Package>
                  <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
                  <Bundle-Name>${pom.name}</Bundle-Name>
               </instructions>
            </configuration>
         </plugin>
      </plugins>
   </build>
   <repositories>
      <repository>
         <id>apache.incubating</id>
         <name>Apache Incubating Repository</name>
         <url>http://people.apache.org/repo/m2-incubating-repository</url>
      </repository>
   </repositories>
   <pluginRepositories>
      <pluginRepository>
         <id>apache.incubating.plugins</id>
         <name>Apache Incubating Plugin Repository</name>
         <url>http://people.apache.org/repo/m2-incubating-repository 
         </url>
      </pluginRepository>
   </pluginRepositories>
   <dependencies>
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>servlet-api</artifactId>
         <version>2.4</version>
      </dependency>
      <dependency>
         <groupId>org.apache.felix</groupId>
         <artifactId>org.osgi.core</artifactId>
         <version>1.0.0</version>
      </dependency>
      <dependency>
         <groupId>org.apache.sling</groupId>
         <artifactId>org.apache.sling.api</artifactId>
         <version>2.0.0.incubator-SNAPSHOT</version>
      </dependency>
      <dependency>
         <groupId>mh.studies</groupId>
         <artifactId>mh.studies.sling.osgitest</artifactId>
         <version>0.0.5</version>
      </dependency>
   </dependencies>
</project>
Maven dependencies: oops

Everything looks easy so far, but there's one catch: no public m2 repository can satisfy the dependency on org.apache.sling.api-2.0.0.incubator-SNAPSHOT, so that when you now run mvn package, you will end up with the following:

D:\projekte\workspace-sling\mh.studies.sling.osgitest.client>mvn package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building A Servlet Client for the OSGi Test Bundle
[INFO]    task-segment: [package]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] snapshot org.apache.sling:org.apache.sling.api:2.0.0.incubator-SNAPSHOT: checking for updates from apache.incubating
Downloading: http://people.apache.org/repo/m2-incubating-repository/org/apache/sling/org.apache.sling.api/2.0.0.incubator-SNAPSHOT/org.apache.sling.api-2.0.0.incubator-SNAPSHOT.pom

Downloading: http://people.apache.org/repo/m2-incubating-repository/org/apache/sling/org.apache.sling.api/2.0.0.incubator-SNAPSHOT/org.apache.sling.api-2.0.0.incubator-SNAPSHOT.jar

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to resolve artifact.

Missing:
----------
1) org.apache.sling:org.apache.sling.api:jar:2.0.0.incubator-SNAPSHOT

  Try downloading the file manually from the project website.

  Then, install it using the command:
      mvn install:install-file -DgroupId=org.apache.sling -DartifactId=org.apache.sling.api -Dversion=2.0.0.incubator-SNAPSHOT -Dpackaging=jar -Dfile=/path/to/file

  Alternatively, if you host your own repository you can deploy the file there:
      mvn deploy:deploy-file -DgroupId=org.apache.sling -DartifactId=org.apache.sling.api -Dversion=2.0.0.incubator-SNAPSHOT -Dpackaging=jar -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]

  Path to dependency:
        1) mh.studies:mh.studies.sling.osgitest.client:bundle:0.0.1
        2) org.apache.sling:org.apache.sling.api:jar:2.0.0.incubator-SNAPSHOT

----------
1 required artifact is missing.

for artifact:
  mh.studies:mh.studies.sling.osgitest.client:bundle:0.0.1

from the specified remote repositories:
  central (http://repo1.maven.org/maven2),
  apache.incubating (http://people.apache.org/repo/m2-incubating-repository)


[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Wed Sep 03 16:56:39 CEST 2008
[INFO] Final Memory: 5M/9M
[INFO] ------------------------------------------------------------------------

Don't let it bring you down -- the problem is easily solved, even if it takes a few steps. Note the lines 21 - 26 above. They already show that there is a solution at hand. Now all we need is the jar file. Don't look on the sling website, it's already on your machine. Go to the place you started CRX Quickstart from, and you'll find it here: ./crx-quickstart/server/runtime/0/_/WEB-INF/resources/bundles/org.apache.sling.api-2.0.0-incubator-SNAPSHOT.jar. Now, you only need to execute the following:

mvn install:install-file -DgroupId=org.apache.sling -DartifactId=org.apache.sling.api -Dversion=2.0.0.incubator-SNAPSHOT -Dpackaging=jar \
  -Dfile=<crx quickstart dir>/crx-quickstart/server/runtime/0/_/WEB-INF/resources/bundles/org.apache.sling.api-2.0.0-incubator-SNAPSHOT.jar
Build, Deploy, Test

All that remains is mvn clean install, and go to http://localhost:7402/system/osgitest/info

No comments: