Latest Posts

Archives [+]

Categories [+]

Authors [+]

Archive for December 2008

    Posted by Michael Marth DEC 23, 2008

    Posted in jcr and link of the day Comments 2

    The Spring JCR connector has been around for a while, but so far there have been few introductory articles that I am aware of (apart from the one on InfoQ). Therefore, I was all the more delighted to come across this hands-on introduction by David Winterfeldt on springbyexample.org.

    Posted by Michael Marth DEC 22, 2008

    Posted in dynamic languages and sling Comment 1

    By now, almost everyone (no matter which is your preferred language) should have enough rope to hang himself with Sling:

    Being a computer language afficionado I simply love this. What's more, now that we have Python: Can I has LOLCode?

    Posted by Sebastian Hoogenberk DEC 22, 2008

    Posted in communique, cq5 and lotw Comments 2

    Messe Frankfurt, our 2nd CQ5 tech-preview customer just went live.

    The project (codename "COBRA") started in June 2008, with a concept phase of about 3 months. Development started end of August, 2008 as a fast-paced, "agile" project, and has its first phase ended by now (so it took about 3.5 months).

    We actually went live with 3 websites, please check them all out: www.messefrankfurt.com, musik.messefrankfurt.com and pls.messefrankfurt.com (the corporate website and 2 exhibitions)

    Thanks go to our supreme project manager, Conrad Wöltge and the whole COBRA-Team, which worked together really well! And of course, big thanks to R&D for dealing with (and really listening to) our numerous enhancement and feature requests, especially to Felix Meschberger, who worked very close with us, and may I say, enthusiastically! It was a real pleasure working in this project!

    Posted by Bertrand Delacretaz DEC 18, 2008

    Posted in sling, sling.gems and xml Comments 3

    In this second post of our Sling Gems series,we describe a very simple way of generating XML, by hooking a script to the .test.xml selector and extension.

    Recent versions of Sling do include a default rendering for the .xml extension,but our script will allow us to customize the XML format, to create RSS feeds, for example.

    A script for the .test.xml selector

    Saving the script shown below as /apps/sling/servlet/default/test.xml.esp willactivate it to process URLs that end with the test.xml selector and extension.

    <%function xml(str) {  if(!str) return "";  return str    .replace(/&/, '&amp;')    .replace(/</g, '&lt;')    .replace(/>/g, '&gt;')  ;}response.setContentType("application/xml");%><test path="<%= xml(currentNode.getPath()) %>"> <type>   <%= xml(currentNode["jcr:primaryType"]) %> </type> <title>   <%= xml(currentNode.title) %> </title></test>

    The xml() escaping function should be provided by Sling as it's fairly common,but right now there's no such utility, so our simple variant will do.

    To test the script, create a node at /foo, for example1:

    curl \  -F title="Hello, XML" \  http://admin:admin@localhost:8888/foo

    And test the XML rendering at http://localhost:8888/foo.test.xml .You should see something like

    <test path="/foo"> <type>nt:unstructured</type> <title>Hello, XML</title></test>

    That's all, folks!

    That's all there is to it. As usual with Sling, placing a script in the appropriatelocation is all that's needed to activate it.

    Keep on scripting, and subscribe to our feed to enjoymore Sling Gems!

    1. This assumes your instance of Sling is running on port 8888. If that's not the case, adjust the example URLs accordingly.

    Posted by Michael Marth DEC 16, 2008

    Posted in atom, atompub, crx and jcr Comment 1

    Apache Abdera is an Apache Software Foundation project to...

    build a functionally-complete, high-performance implementation of the IETF Atom Syndication Format (RFC 4287) and Atom Publishing Protocol (RFC 5023) specifications.

    (if reading RFCs is not your cup of tea: IBM developerworks has some good tutorials on AtomPub. Update: Julian Reschke pointed me to these HTML versions of the Publishing and Syndication spec which are quite a bit easier on the eyes).

    The project has recently graduated from the incubator and is now a top level project at the ASF. Congratulations!

    Since release 0.4 Abdera comes with a JCR adapter (JCR-loves-Atom, remember?). The JCR adapter allows you to run an Abdera AtomPub server and have a Jackrabbit repository be used for storage.

    Getting it to run is quite easy: do a checkout of the 0.4 release at http://svn.apache.org/repos/asf/abdera/java/tags/abdera-0.4.0-incubating/ and built with

    mvn clean install
    

    This command will also execute the test cases in adapters/jcr/src/test/java/org/apache/abdera/jcr. As part of the test cases a JCR-backed Atom server gets started and some JCR nodes are created through Atom. The main magic happens in the JcrCollectionProvider class that maps the AtomPub elements onto JCR nodes. For example, AtomPub's ID is mapped onto JCR's UUID:

    public String getId(Node entry) throws ResponseContextException {
     try {
      return "urn:" + entry.getUUID();
     } catch (RepositoryException e) {
      throw new ResponseContextException(500, e);
     }
    }
    

    Getting Abdera to interact with CRX

    The primary purpose of the JcrCollectionAdapter class is to equip a stand-alone Atom server with a JCR repository for storage. However, with a bit of tweaking the class can also be used to provide an Atom interface to an existing CRX repository: a simple way to get things running is to leave the existing CRX Quickstart untouched and connect to the repository through RMI. RMI is disabled by default, but on CRX's Knowledge Base is an article how to enable it.

    Abdera bundles Jetty which can be used to provide the http server for AtomPub. I have wrapped the JcrCollectionProvider in a little servlet and initialized Jetty with this servlet. The class looks like this:

    import org.apache.jackrabbit.rmi.client.ClientRepositoryFactory;
    import javax.jcr.Repository;
    import javax.jcr.Session;
    import javax.jcr.SimpleCredentials;
    import javax.servlet.http.HttpServlet;
    import org.apache.abdera.protocol.server.Provider;
    import org.apache.abdera.protocol.server.impl.DefaultProvider;
    import org.apache.abdera.protocol.server.impl.SimpleWorkspaceInfo;
    import org.apache.abdera.protocol.server.servlet.AbderaServlet;
    import org.mortbay.jetty.Server;
    import org.mortbay.jetty.servlet.Context;
    import org.mortbay.jetty.servlet.ServletHolder;
    
    public class AppServer {
    
     public static void main(String... args) throws Exception {
      int port = 9002;
      try {
       port = args.length > 0 ? Integer.parseInt(args[0]) : 9002;
      } catch (Exception e) {
      }
      Server server = new Server(port);
      Context context = new Context(server, "/", Context.SESSIONS);
      ServletHolder servletHolder = new ServletHolder(new
      JcrCollectionServlet());
      context.addServlet(servletHolder, "/*");
      server.start();
      server.join();
     }
    
     public static final class JcrCollectionServlet extends AbderaServlet {
      protected Provider createProvider() {
    
       DefaultProvider jcrProvider = new DefaultProvider();
    
       try {
    
        ClientRepositoryFactory factory = new ClientRepositoryFactory();
        Repository repository = factory.getRepository("//localhost:1234/crx");
    
        JcrCollectionAdapter cp = new JcrCollectionAdapter();
        cp.setTitle("My Entries");
        cp.setAuthor("Apache Abdera");
        cp.setCollectionNodePath("entries");
        cp.setRepository(repository);
        cp.setCredentials(new SimpleCredentials("admin", "admin".toCharArray()));
        cp.setHref("feed");
        cp.initialize();
    
        SimpleWorkspaceInfo wkspc = new SimpleWorkspaceInfo();
        wkspc.setTitle("JCR Workspace");
        wkspc.addCollection(cp);
        jcrProvider.addWorkspace(wkspc);
        jcrProvider.init(getAbdera(), getProperties(getServletConfig()));
       } catch (Exception e) {
        return null;
       }
       return jcrProvider;
      }
     }
    }
    

    Please note that the servlet just needs to acquire the JCR and pass a reference to it to the JcrCollectionProvider. Thus, this approach can be used with JNDI or other means of connection as well and is not specific to RMI. Before you run the server you will have to perform a little tweak in the JcrCollectionProvider: when the provider is started it will try to register a new node type. AFAIK this does not work via RMI (I might be wrong, though), so I commented this code in the initialize() method :

    //JackrabbitNodeTypeManager jntmgr = (JackrabbitNodeTypeManager) workspace.getNodeTypeManager();
    //if (!jntmgr.hasNodeType("abdera:entry")) {
     //InputStream in = getClass().getResourceAsStream("/org/apache/abdera/jcr/nodeTypes.xml");
     //try {
      //jntmgr.registerNodeTypes(in, JackrabbitNodeTypeManager.TEXT_XML);
     //} finally {
      //in.close();
     //}
    //}
    

    and manually added the the namespace abdera="http://abdera.apache.org" and the node type "abdera:entry" defined in adapters/jcr/src/main/resources/org/apache/abdera/jcr/nodeTypes.xml to CRX (find a link to the node type manager that provides a UI for registering namespaces and node types at http://localhost:7402/crx). The JcrCollectionProvider will use its specific node type abdera:entry to store and retrieve data.

    After you have defined abdera:entry run the AtomPub server main class from above and find your Atom interface at http://localhost:9002/feed (make sure to include the CRX RMI jars from crx-quickstart/server/runtime/0/_crx/WEB-INF/lib on your classpath). You create nodes via curl (see here for an example) or (more or less convenient, depending on your taste) via the AbderaClient class like this code below. It creates a new post with some example data:

    import java.util.Date;
    import javax.jcr.Repository;
    import org.apache.abdera.Abdera;
    import org.apache.abdera.factory.Factory;
    import org.apache.abdera.i18n.iri.IRI;
    import org.apache.abdera.model.Entry;
    import org.apache.abdera.protocol.client.AbderaClient;
    import org.apache.abdera.protocol.client.RequestOptions;
    import org.apache.abdera.protocol.server.impl.DefaultProvider;
    
    // this code is taken from Dan Diephous's
    // test classes for the JcrCollectionProvider 
    public class AppServerTest {
    
     private DefaultProvider jcrProvider;
     private Repository repository;
    
     public static void main(String... args) throws Exception {
      Abdera abdera = new Abdera();
      Factory factory = abdera.getFactory();
    
      AbderaClient client = new AbderaClient(abdera);
    
      String base = "http://localhost:9002/";
    
      // Testing of entry creation
      IRI colUri = new IRI(base).resolve("feed");
      Entry entry = factory.newEntry();
      entry.setTitle("Some Entry");
      entry.setUpdated(new Date());
      entry.addAuthor("Dan Diephouse"); // thanks, for the provider, Dan!
      entry.setId(factory.newUuidUri());
      entry.setSummary("This is my entry.");
      entry.setContent("This is my entry. It's swell.");
    
      RequestOptions opts = new RequestOptions();
      opts.setContentType("application/atom+xml;type=entry");
      ClientResponse res = client.post(colUri.toString(), entry, opts);
     }
    }
    

    (the example is taken from Dan Diephouse's test classes for the JcrCollectionProvider)

    This might be your first step if you want to offer an AtomPub interface to your CRX repository. However, it is likely that you will have to tweak the JcrCollectionProvider class a bit more: the crucial part is the mapping between JCR nodes and AtomPub elements. As discussed above the out-of-the-box JcrCollectionProvider can only deal with nodes of type abdera:entry. However, this will not suffice to Atom-enable an existing application, the mapping needs to fit to your specific application's node types.

    Somewhat related to AtomPub and JCR: Apache Jackrabbit is expected to support CMIS in the future (which includes an AtomPub binding). Work has started on this and there is some initial code in the sandbox.

    Posted by Michael Marth DEC 15, 2008

    Posted in quote and sling Add comment

    From "Catching up on XForms, XRX, XProc, and Orbeon" on ecmarchitect.com:

    Thinking I might want to use Apache Sling/Jackrabbit as my repository, I decided to see how easy it would be to persist the XForms data into Jackrabbit instead of eXist, as Orbeon’s tutorial does by default. As I suspected, it turned out to be a 2 minute task.

    Posted by Bertrand Delacretaz DEC 15, 2008

    Posted in javascript, sample, sling and sling.gems Comments 15

    This is our first post in the Sling Gems series, that we'll use to demonstrate our favorite Sling features.

    In this episode, the SlingPostServlet and the sling.js library are brought together using 46 (no kidding: fourty-six) lines of code to create a simple blog (or let's say bloggish) application.

    I used this example in my Rapid JCR applications development with Sling presentation at the last ApacheCon (slides are available), and I think it's a good testimony to the power and simplicity of Sling.

    Step 1: Creating content

    The easiest way to create content in Sling is to use an HTTP POST request, let's use a simple HTML form to do that:

    <html>
      <body>
        <h1>Sling microblog</h1>
      
        <div>
          <form method="POST">
            Title:<br/>
            <input type="text" name="title" 
              style="width:100%"/>
            
            <br/>Text:<br/>
            <textarea style="width:100%" name="text">
            </textarea>
            
            <br/>
            <input type="submit" value="save"/>
            <input type="hidden" name=":redirect" 
              value="*.html"/>
          </form>
        </div>
      
        <!-- code of step 2 comes here -->
      </body>
    </html>
    

    That's two input fields, a submit button and a hidden field that tells Sling what to do after the POST (in this case: redirect to the html view of the node that was just created).

    To test the form, start Sling and save the above script as /apps/blog/blog.esp in the Sling repository - a WebDAV mount is the easiest way to do that. Browsing to http://localhost:8888/content/blog/*.html2 should display the above form 4.

    Input some data (using "foo" for the title, for the sake of our examples below), save the form, and Sling should display the form again, using the URL of the node that was just created.

    At this point you're probably looking at an empty form with an URL ending in foo, if you used that for the title. Or foo_0 or foo_1 if other foos already existed. Don't worry about not seeing your content, we'll fix that right away.

    Step 2: Where's my content?

    To verify that our content has been created, we can have a look at the JSON data at http://localhost:8888/content/blog/foo.tidy.json, which should display our new node's values:

    {
      "jcr:primaryType": "nt:unstructured",
      "text": "This is the foo text",
      "title": "foo"
    }
    

    That's reassuring, but what we really want is for these values to be displayed on the editing form for our post.

    Thanks to the sling.js client library, we just need to add a Sling.wizard() call to our form to display those values. Let's first add a <head> element to our form to load the sling.js library, before the existing <body> of course:

    <head>
      <script src="/system/sling.js"></script>
    </head>
    

    And add the Sling.wizard() after the form, where we had the code of step 2 comes here comment:

    <!-- code of step 2 comes here -->
    <script>Sling.wizard();</script>
    

    Reloading the form at http://localhost:8888/content/blog/*.html and creating a new post should now redirect to an editable version of the post, with the form fields correctly initialized.

    We can now create and edit posts; let's add some navigation, using more of the sling.js functionality.

    Step 3: Navigation

    The sling.js library provides utilities to access and manipulate content. Four our blog, we'll use the getContent(path) method to list the siblings of the current node.

    Add the following code to your script, after the Sling.wizard() call that was added in step 2:

    <h3>Navigation</h3>
    <ul>
        <li><em><a href="/content/blog/*.html">
          [Create new post]</a></em></li>
        <script>
          var posts = 
            Sling.getContent("/content/blog", 2);
          for(var i in posts) {
            document.write("<li>"
              + "<a href='/content/blog/" + i + ".html'>" 
              + posts[i].title 
              + "</a></li>");
          }
        </script>
    </ul>
    

    The first link to /content/blog/* brings us back to our content creating form, which is nothing else than the editing form reading empty values and posting to the "magic star" URL.

    The rest of the javascript runs client-side, as it is not embedded in <% %> code markers, calls the sling.getContent method to get two levels of node data below /content/blog, and displays links to nodes that it finds.

    That's a basic navigation, of course, in a real blog we'd need some paging and contextualization to cope with large numbers of posts.

    Nevertheless, with this addition our ESP script allows us to create, edit and navigate blog posts - not bad for 46 lines of code, including comments, whitespace and output formatting.

    Step 4: Data first, structure later

    You might have heard this mantra, which we apply in many areas of Sling.

    In this case, adding a new field to our blog posts could not be easier: just add an input field to the form, and Sling will do the rest.

    Adding this inside our script's <form> element, for example:

    <br/>Author:<br/>
    <input type="author" name="author" 
      style="width:100%"/>
    

    Allows us to add an author name to our blog posts. No need to define anything at the repository level, as Sling is using it in unstructured mode in this case, and no need to migrate existing data, the author field of existing posts will simply be empty.

    I want my ESP!

    Now wait...we said we were going to create an ESP script, but our "application" is just static HTML and some client javascript at this point.

    That's correct - as we are using only Sling client-facing features at this point (HTTP POST and sling.js), we do not necessarily need to use ESP code.

    To keep things simple, we'll refrain from adding ESP-based features at this point, and keep that for a future Sling Gem.

    That's the power of Sling

    The 46-line blog is a good example of the power of Sling. It leverages the SlingPostServlet3, which handles POST requests in a form-friendly way, and the sling.js client library, which provides high-level functionality on the client side.

    A slightly fancier version of the blog.esp script is attached to this post, the code is the same but some CSS makes it look a bit nicer.

    Let us know what you think, and stay tuned for more Sling Gems episodes!

    1. ESP is Sling's server-side javascript language.
    2. This assumes your instance of Sling is running on port 8888. If that's not the case, adjust the example URLs accordingly.
    3. See Manipulating Content: The SlingPostServlet for a more complete description of the Sling POST servlet.
    4. Note that this requires disabling anonymous access to Sling pages: uncheck the Allow Anonymous Access setting on the Sling Request Authenticator configuration page at http://localhost:8888/system/console/config, and save that configuration.

    Posted by Michael Marth DEC 09, 2008

    Posted in jackrabbit Comments 3

    Next to XPath and SQL Jackrabbit 1.5 supports a third query language: "Google-style". It is basically syntatic sugar above an XPath query, but a welcome addition nevertheless. David Nuescheler has posted a good example on the Jackrabbit developer list on how to use it:

    foo // -> will find foo in all properties
    title:foo // -> will find foo in a title properties
    title:foo bar // -> will find all nodes that contain bar and have foo in its title
    author:torgeir // -> will find all nodes that have a property author that contains torgeir
    

    The Javadoc contains more useful examples like

    path: only search nodes below this path.
    type: only return nodes of the given node types.
    

    Sigh, this will be so much easier to remember than XPath. Btw, Kas Thomas has pointed out that the acronym GQL is already in use for the Google App Engine query language. How about GooQL? (J)CRQL? RabbitQL? C(ontent)QL?

    Posted by Michael Marth DEC 09, 2008

    Posted in crx and jackrabbit Comment 1

    One of the better-hidden features of CRX is that CRX is "Drop-in" compatible with Apache Jackrabbit:

    A Day CRX release is based on the latest unmodified official Jackrabbit release. Furthermore, starting with CRX v1.4 release, Day CRX and Apache Jackrabbit are “code drop-in” compatible with the following benefits:[...]

    Yesterday, I was curious to try my luck and find out what that actually means. For one thing the JCR standard gurantees compatibility of applications. If an application should additionally use Jackrabbit's non-JCR classes the application will also work in CRX because these classes are all available and unmodified in CRX (for example, I find org.apache.jackrabbit.util.Text quite helpful). Therefore, apart from getting the session object no code should have to change.

    For the content it turns out that things are very simple as well. You can just grab a Jackrabbit workspace directory and copy it in CRX. If you have defined custom namespaces and node types just delete the respective files in CRX and copy over the Jackrabbit files (assuming you have a fresh clean CRX installation). CRX will merge your custom definition with its own definitions. So that's the whole import (shutdown CRX and Jackrabbit before you do this):

    cp $JRHOME/repository/namespaces/* $CRXHOME/repository/namespaces/
    cp $JRHOME/repository/nodetypes/custom_nodetypes.xml $CRXHOME/repository/nodetypes/
    cp -R $JRHOME/repository/workspaces/default  $CRXHOME/repository/workspaces/
    

    Start CRX and -voila- there are the node types and the new migrated workspace. The resulting workspace will, of course, still run on the Derby Persistence Manager. For getting it into the TarPM you would still have to export/import the content.

    Displaimer: if you want to try this on your production servers: please talk to our support guys first... I was just playing around.

    Posted by Michael Marth DEC 08, 2008

    Posted in jackrabbit Add comment

    Jackrabbit 1.5 has just been released. Congratulations to the dev team! Jukka Zitting has the most important improvements on his blog:

    IMO the standalone server will make it easier for newbies to get started. Good stuff.

    Posted by Gerd Handke DEC 04, 2008

    Posted in cms and communique Comment 1

    The new web presence of City of Zurich (www.stadt-zuerich.ch) has been launched today. This is the first public website based on CQ5, not counted Day's web properties. Congratulations to all who contributed to this great success.

    From my perspective the experience and feedback from this project greatly validated the direction we took with CQ5. We really like reviews like this one on InfoWorld but even better (at least from a Product Manager's perspective) is the positive validation in a real life customer project. A big thank you goes to City of Zurich for the confidence in Day's new technology to start very early with the work on CQ5.

    Posted by Michael Marth DEC 02, 2008

    Posted in link of the day Add comment

    Bertrand Delacretaz (developer at Day, Sling committer and board member of the Apache Software Foundation) has been interviewed by the (unofficial) Apache podcast Feathercast about the Sling project. Multi-talented as he is, Bertrand has also composed Feathercast's title music.