Latest Posts

Archives [+]

Categories [+]

Authors [+]

Entries filed under 'tutorial'

    Posted by Jean-Christophe Kautzmann AUG 24, 2010

    Posted in sling and tutorial Comments 3

    Apache Sling enables you to manage events within your application. Events can be used for example to trigger workflows or to run a business process like sending an email or managing assets that have been ingested into your application.

    The event mechanism is leveraging the OSGi Event Admin Specification. The OSGi API is very simple and leightweight. Sending an event is just generating the event object and calling the event admin. Receiving the event is implementing a single interface and declaring through properties which topics one is interested in.
    Sling introduces a special category of events called job events: unlike basic events, job events are garanteed to be processed. In other words: someone has to do something with the job event (do the job).

    For more details on the eventing mechanisms in Sling you can refer to the Eventing, Jobs and Scheduling page in the Sling documentation
    and to the javadocs for the OSGI and Sling API, especially the packages org.osgi.service.event and org.apache.sling.event.

    Another resource to get your hands on code is a step-by-step tutorial I created for the Sling documentation: it shows you how to listen to files uploaded to a temporary location in your web application and to move them to a specific location depending on their MIME types.

    Have fun eventing with Sling!

    Posted by Kas Thomas JUL 16, 2010

    Posted in content management, crx, crx gems, development, http, javascript, modelling, rest and tutorial Comment 1

    In a recent blog, I talked about how easy it is to store snippets of text from OpenOffice in a CRX repository using a little bit of JavaScript and the Sling REST API. While being able to store arbitrary bits of text this way is certainly useful, it would be even more useful to be able to store spreadsheet data. Of course, storing a spreadsheet in CRX, per se, is not much of a challenge: with WebDAV, it's a matter of drag and drop. But storing an entire spreadsheet as a single monolithic content item doesn't necessarily give you the greatest content-management bang for the buck. Often, what you really want to do is granularize the spreadsheet into records (or row data), and store individual rows as content items. (You could take it further and store individual cells as content items, but that would probably be overkill for most situations, although there's certainly nothing preventing you from doing it.)

    In the database world, where decisions often have to be made as to how best to decompose an XML document when mapping it to tables in a database, this general process (of decomposing a large document along the lines of its natural internal fine-structure) is known as shredding. What would be handy is to have an OpenOffice macro that could shred a spreadsheet into rows, and push the rows into nodes in CRX. That's what I propose to show you right now.

    It turns out to be pretty easy to parse a spreadsheet in an OpenOffice macro. Using JavaScript:

       // First, get the document object
       // from the scripting context
       oDoc = XSCRIPTCONTEXT.getDocument();

       // Next, get the XSpreadsheetDocument
       // interface from the document
       xSDoc = UnoRuntime.queryInterface(XSpreadsheetDocument, oDoc);

       // Then get a reference to the sheets for this doc
       var sheets = xSDoc.getSheets();

       // get Sheet1
       var sheet1 = sheets.getByName("Sheet1");

    Once you've gotten the sheet reference, you can use it to obtain a cell reference:

    var cell = sheet.getObject().getCellByPosition( column, row );

    The cell, in turn, contains data, which (dependening on whether you're dealing with a native OpenOffice spreadsheet versus a freshly imported CSV file) can be a floating-point value, a string, or something else. For purposes of this discussion I'm going to assume that you've just imported a CSV or tab-delimited file into OpenOffice, in which case all cells will automatically contain string data. To get the string data from a cell in a freshly imported CSV file, you have to do:

    var content = cell.getFormula();

    At least, that's what works in OpenOffice 3.2.

    The general plan of attack, then, is to come up with a function that can parse a row's worth of data out of a spreadsheet; and have another function that can persist a row of data as a content item in CRX. Then it should be possible to create a macro that simply loops over all rows in a spreadsheet and pushes them out to the repository.

    The row-parsing function is pretty straightforward:

    function getRow( sheet, rownumber, startColumn, endColumn )  {

        var obj = sheet.getObject();
        var record = [];

        for (var k = startColumn; k < endColumn ; k++) {
             var cell = obj.getCellByPosition( k, rownumber );
             var content = cell.getFormula();
             record.push( content );
        }

        return record;
    }

    Given a reference to a Sheet, along with a row number and the starting and ending column numbers, this function loops through cells and pushes cell values into an array. The returned array represents a row's worth of data.

    To persist a row to CRX, we have a function that looks like this:

    function persistRow( sheet, rownumber, startColumn, endColumn ) {

       // get first row of data (column names)
       var columnNames = getRow( sheet, 0, startColumn, endColumn );

       // get specified record
       var row = getRow( sheet, rownumber, startColumn, endColumn );

       // build the request
       var request = {};
       request[":nameHint"] = row[2]; // Title
       request["sling:resourceType"] = "films";
       for ( var i = 0; i < columnNames.length; i++) {
           request[ columnNames[ i ] ] = row[ i ];
       }   
       var data = createRequest( request );

       // where to store it
       var url = "http://localhost:7402/content/films/";

       // finally, hit the repository
       var response = doJavaPOST( url, data );

       return response;
    }

    Notice that the code assumes that the first row of "data" in the spreadsheet contains the column names. This was in fact the case with the test-spreadsheet I used for testing this macro, namely a spreadsheet called a1-film.csv, representing 1741 movies catalogued by Georgia Tech's College of Computing. Each row in the spreadsheet has information for a particular film, such as the film's title, the year the film was made, its genre, the name of the director, major actors and actresses, etc.

    Without further ado, here is the complete code for the OpenOffice macro:



    // Spreadsheet2CRX Macro
    // Kas Thomas, 15 July 2010
    // Public domain. Use at your own risk.
    // Tested with v3.2 of OpenOffice.org

    importClass(Packages.com.sun.star.uno.UnoRuntime);
    importClass(Packages.com.sun.star.sheet.XSpreadsheetDocument);

    // Do a POST
    function doJavaPOST( url, content ) {
            var reply = "";
            var responseCode = "";
            try {
                    var URL = new java.net.URL( url );
                    var urlConn =
                       URL.openConnection( );
                    urlConn.setDoOutput ( true );
                    urlConn.setRequestMethod( "POST" );
                    urlConn.setUseCaches( false );
                    urlConn.setRequestProperty ("Content-Type",
                    "application/x-www-form-urlencoded" );
                    var printout =
                    new java.io.DataOutputStream ( urlConn.getOutputStream ( ) );
                    printout.writeBytes ( content );
                    printout.flush ( );
                    printout.close ( );
                    responseCode = urlConn.getResponseCode();
            }
            catch(exception) {
                    java.lang.System.out.println( exception.toString() );
            }

            return responseCode;
    }

    // munge together the form data
    // into "name1=value1&name2=value2" etc
    function createRequest( object ){

            var data = [];
            for ( var i in object )
            data.push( i + "=" + object[ i ].toString( ) );

            var dataString = data.join( "&" );
            return dataString;
    }

    // Modal dialog with OK/cancel and a text field
    function prompt( msg ) {
            var swing = Packages.javax.swing;
            var text = swing.JOptionPane.showInputDialog(
            new java.awt.Frame(), msg );
            return ( null == text ) ? "" : text; // always return a string
    }

    // a Swing UI for displaying console info
    function EditorPane( ) {

            Swing = Packages.javax.swing;
            this.pane = new Swing.JEditorPane("text/html","" );
            this.jframe = new Swing.JFrame( );
            this.jframe.setBounds( 100,100,500,400 );
            var editorScrollPane = new Swing.JScrollPane(this.pane);
            editorScrollPane.setVerticalScrollBarPolicy(
            Swing.JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
            editorScrollPane.setPreferredSize(new java.awt.Dimension(250, 250));
            editorScrollPane.setMinimumSize(new java.awt.Dimension(10, 10));
            this.jframe.setVisible( true );
            this.jframe.getContentPane().add( editorScrollPane );

            // public methods
            this.getPane = function( ) { return this.pane; }
            this.getJFrame = function( ) { return this.jframe; }
    }

    function getRow( sheet, rownumber, startColumn, endColumn )  {

            var obj = sheet.getObject();
            var record = [];

            for (var k = startColumn; k < endColumn ; k++) {
                    var cell = obj.getCellByPosition( k, rownumber );
                    var content = cell.getFormula();
                    record.push( content );
            }

            return record;
    }

    function persistRow( sheet, rownumber, startColumn, endColumn ) {

            // get first row of data (column names)
            var columnNames = getRow( sheet, 0, startColumn, endColumn );

            // get specified record
            var row = getRow( sheet, rownumber, startColumn, endColumn );

            // build the request
            var request = {};
            request[":nameHint"] = row[2]; // Title
            request["sling:resourceType"] = "films";
            for ( var i = 0; i < columnNames.length; i++) {
                    request[ columnNames[ i ] ] = row[ i ];
            }
            var data = createRequest( request );

            // where to store it
            var url = "http://localhost:7402/content/test/";

            // finally, hit the repository
            var response = doJavaPOST( url, data );

            return response;
    }

    ( function main( ) {

            //get the document object from the scripting context
            oDoc = XSCRIPTCONTEXT.getDocument();

            //get the XSpreadsheetDocument interface from the document
            xSDoc = UnoRuntime.queryInterface(XSpreadsheetDocument, oDoc);

            // get a reference to the sheets for this doc
            var sheets = xSDoc.getSheets();

            // get Sheet1
            var sheet1 = sheets.getByName("Sheet1");

            // construct a new EditorPane
            var editor = new EditorPane( );
            var pane = editor.getPane( );

            var size = prompt("Enter total rows and total columns, separated by a comma (e.g., '100,8')");
            if ( !size )
            return "No row/column info supplied.";

            var rows = Number( size.substring(0,size.indexOf(",")) );
            var cols = Number( size.substring( size.indexOf(",")+1) );

            var errors = 0;
            for ( var i = 1; i <= rows; i++) {
                    var response = persistRow( sheet1, i, 0, cols );
                    var text = pane.getText();
                    pane.setText( text + "\nProcessing: " + i );
                    if ( response.toString().indexOf("5")==0 )
                    errors++;
                    // provide a little bit of throttling:
                    java.lang.Thread.sleep( 200 );
            }
            pane.setText( pane.getText() + "\n" + errors + " errors" );
    })();




    You'll notice that the code creates a JEditorPane window to act as an error console. When you run the macro, a JOptionPane dialog appears, asking you to supply the number of rows and columns in the spreadsheet. (For the Georgia Tech spreadsheet, you can enter "1741,8", minus quotes.) Once you dismiss the dialog, the code goes to work looping over all the rows in the spreadsheet, posting each row to CRX at a path of http://localhost:7402/content/films/.

    Each new node is named according to a :nameHint parameter based on the Title of the film.

    Notice also, we designate a sling:resourceType for each node of "films." (This happens in the persistRow() function.) This fact will be important in a later blog when I show how to write server-side scripts that handle various types of requests for film data.

    And that's about it: Now you know how to shred a spreadsheet (say that 3 times in a row fast...) and store the results in CRX, using OpenOffice.

    Posted by Greg Klebus JUN 16, 2010

    Posted in crx, crx gems, sample and tutorial Add comment

    One of my favorite ways of learning products and technologies has always been looking at example applications and code samples, as well as following step-by-step tutorials to create simple pieces of functionality. Nothing will give you a better feel for what you are dealing with, and whether it is a good match for your needs.

    I thought I would compile a list of example applications, code samples, and tutorials available for CRX now to help those interested choose the right examples.

    Example Applications With Sources

    All the example applications listed below are on Package Share, in Public » Day » CRX 2.1.0 » example folder. Simply start CRX 2.1 Developer Edition, click the Share button, and follow Package Share documentation to get access to them. Or you can click this link to show all the example applications if you have your CRX running on the default 7402 port on your local machine.

    StatusUpdate Example Application. It is a sample application that mimics Twitter.com. It is designed to help get started building applications on CRX using the JCR API and Sling.

    Some features that are used:

    • different renditions of content (e.g. as HTML or as an RSS feed)
    • search based on XPATH queries and the Query Object Model introduced in JCR 2.0
    • handling security, authentication and authorization ans well as setup of users and groups
    • leveraging the built-in JSON renditions for AJAX user interfaces

    CRX Bookstore Example Application. This is a sample online bookstore application for CRX, with source code hosted on Google Code. This package provides a quick way of looking at the application and its source code - just install the package, and click on the "Bookstore application" link on the welcome screen (or go to /products.html on your instance). You can also check out the application directly from source code hosted at project's Subversion server by following the First steps with CRX guide.

    CRX Microblog Example Application. This package contains one-file, 46-line example of a micro blog in Sling. The source code is contained in /apps/blog/blog.esp script. The example was attached to Bertrand Delacretaz's Sling gems: a blog in 46 lines of code blog post.

    Tutorials

    Web Development with CRX 15-minute screencast, which walks you through main product modules and technologies while presenting the CRX Bookstore example application.

    Getting Started for Developers documentation page, including a step-by-step example of developing a simple blog in CRX, showing rendering of content (with multiple rendering of the same resource, like HTML and RSS), adding styling with CSS, basic error handling, displaying images, and handling user comments.

     

    Feel free to leave a comment in case I missed something interesting and I'll update the list. Also, in case you have an interesting example or code sample to share, drop me a line at gklebus at day dot com, and I'll help get it published!

    Posted by Michael Marth APR 01, 2010

    Posted in link of the day, sling and tutorial Comment 1

    Apache Sling implements the concept of a "resource". In many cases the actual implementation of the resource is a JCR node. However, Sling can be extended with other implementations of the ResourceProvider interface. With such an extension Sling will map a URL not to a JCR node, but something else.

    In this context Lucas Masini has published a step-by-step tutorial on how to expose rows of relational databases as (read-only) resources within Sling.

    Lucas also mentions that there are a number of possibilities to integrate relational databases with Sling. If you want to learn more about this topic check out this great thread on the CQ list.

    Posted by Michael Marth MAR 01, 2010

    Posted in osgi, sling and tutorial Comment 1


    In Apache Sling the JCR nodes usually determine the URLs of a Sling-based web app (and consequently for CRX or CQ5-based apps). For example, Sling will look for the resource http://www.example.com/content/a/b at the path /content/a/b in Sling's JCR repository. This 1-1 mapping is often useful, but there are valid use cases for alternative/additional mappings, for example a web site migration from a legacy site onto Sling.

    In simple scenarios Sling's vanity url mechanism can be used: adding a property "sling:vanityUrl" with value, say, "/old/legacy/path" will render the node for the legacy URLs as well. sling:vanityUrl is a multi-valued property, so an arbitrary number of legacy URLs can be specified. As the property's name implies it is also helpful for mapping short URLs (that can easily be communicated) to nodes that are potentially deep down a content hierarchy.

    In more complex scenarios one can resort to Sling servlets. These are servlets deployed into Sling's as OSGi bundles. Obviously, inside of a servlet any arbitrarily complex business logic can be implemented (e.g. for mapping legacy URLs to new ones). The URLs that a servlet shall respond to can be specified through annotations.

    An easy way to create such a servlet is by using CRXDElite, the web-based IDE that ships with CQ5.3. Here's how to do it:

    • Fire up your browser and point it to /crxde at your CQ5.3 installation
    • Create a folder "myapp" in /apps
    • Create a new OSGi bundle and add a servlet

    Here is the scaffolding code for the servlet implementation that extends the convenience class SlingAllMethodsServlet:

    package com.day.dev.servlets;

    import java.io.IOException;
    import javax.servlet.ServletException;
    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.api.SlingHttpServletResponse;
    import org.apache.sling.api.servlets.SlingAllMethodsServlet;
    /** * @scr.service interface="javax.servlet.Servlet"
     * @scr.component immediate="true" metatype="no"
     * @scr.property name="service.description" value="my servlet"
     * @scr.property name="service.vendor" value="Day"
     */
    @SuppressWarnings("serial")
    public class MySlingAllMethodsServlet extends SlingAllMethodsServlet {

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

        @Override
        protected void doPost(SlingHttpServletRequest request,
                SlingHttpServletResponse response) throws ServletException,
                IOException {
            // do something
        }
    }

     

    • Finally, deploy the servlet by executing Tools > Build bundle. Your new bundle should now show up in the OSGi console at /system/console/bundles

    An important aspect are the annotations that determine for which URLs Sling will invoke the servlet. The most important annotations in this context are:

    • sling.servlet.extensions: The URL extensions supported by the servlet for GET requests
      e.g.:
      @scr.property name="sling.servlet.extensions" values.0 = "TEST_EXT_1" values.1 = "TEST_EXT_2"
    • sling.servlet.methods: The request methods supported by the servlet
      e.g.:
      @scr.property name="sling.servlet.methods" values.0="GET" values.1="POST"
    • sling.servlet.paths: The name of the absolute paths under which the servlet is accessible as a resource
      e.g.:
      @scr.property name="sling.servlet.paths" value="/old/legacy/path"