Latest Posts

Archives [+]

Entries filed under 'http'

    Posted by Kas Thomas AUG 16, 2010

    Posted in content management, crx, http, jcr, request and sling Comments 2

    The first version of this post originally was published here.

    One of the things that gives Apache Sling a great deal of power and flexibility is the way it resolves script URLs. Consider a request for the URL

    /content/corporate/jobs/developer.html

    First, Sling will look in the repository for a file at exactly this location. If such a file is found, it will be streamed out as is. But if there is no file to be found Sling will look for a repository node located at:

    /content/corporate/jobs/developer

    (and will return 404 if no such node exists). If the node is found, Sling then looks for a special property on that node named "sling:resourceType," which (if present) determines the resource type for that node. Sling will look under /apps (then /lib) to find a script that applies to the resource type. Let's consider a very simple example. Suppose that the resource type for the above node is "hr/job." In that case, Sling will look for a script called /apps/hr/job/job.jsp or /apps/hr/job/job.esp. (The .esp extension is for ECMAScript server pages.) However, if such a script doesn't exist, Sling will then look for /apps/hr/job/GET.jsp (or .esp) to service the GET request. Sling will also count apps/hr/job/html.jsp (or .esp) as a match, if it finds it.

    Where things get interesting is when selectors are used in the target path. In content-centric applications, the same content (the same JCR nodes, in Sling) must often be displayed in different variants (e.g., as a teaser view versus a detail view). This can be accomplished through extra name steps called "selectors." For example:

    /content/corporate/jobs/developer.detail.html

    In this case, .detail is a selector. Sling will look for a script at /apps/hr/job/job.detail.esp. But /apps/hr/job/job.detail.html.esp will also work.

    It's possible to use multiple selectors in a resource URL. For example, consider:

    /content/corporate/jobs/developer.print.a4.html

    In this case, there are two selectors (.print and .a4) as well as a file extension (html). How does Sling know where to start looking for a matching script? Well, it turns out that if a file called a4.html.jsp exists under a path of /apps/hr/jobs/print/, it will be chosen before any other scripts that might match. If such a file doesn't exist but there happens to be a file, html.jsp, under /apps/hr/jobs/print/a4/, that file would be chosen next.

    Assuming all of the following scripts exist in the proper locations, they would be accessed in the order of preference shown:

    /apps/hr/jobs/print/a4.html.jsp
    /apps/hr/jobs/print/a4/html.jsp
    /apps/hr/jobs/print/a4.jsp
    /apps/hr/jobs/print.html.jsp
    /apps/hr/jobs/print.jsp
    /apps/hr/jobs/html.jsp
    /apps/hr/jobs/jobs.jsp
    /apps/hr/jobs/GET.jsp

    This precedence order is somewhat at odds with the example given in SLING-387. In particular, a script named print.a4.GET.html.jsp never gets chosen (nor does print.a4.html.jsp). Whether this is by design or constitutes a bug has yet to be determined. But in any case, the above precedence behavior has been verified.

    For more information on Sling script resolution, be sure to consult the (excellent) Sling Cheat Sheet as well as Michael Marth's previous post on this topic. (Many thanks to Robin Bussell at Day Software for pointing out the correct script precedence order.)

    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 Kas Thomas JUL 12, 2010

    Posted in ajax, crx, crx gems, development, http, javascript and sling Add comment

    Not long ago, I wrote about possible ways to get interaction to happen between Adobe Acrobat and Day CRX, and I gave an example of how to use a PDF form to push content into CRX. That's the simplest way to get content into the repository using Acrobat, but it's certainly not the only way. As it turns out, Acrobat's JavaScript API supports more sophisticated AJAX-style asynchronous communication back and forth between Acrobat and a host. That's what I'd like to talk about now.

    There are some important differences between what I'll call Acrobat AJAX and ordinary (browser) AJAX. The most important difference is that with Acrobat (and here, I'm talking about Acrobat Professional, not Acrobat Reader; unlike my last blog, everything we're going to talk about today requires a full copy of Acrobat), your AJAX scripts are scoped to the application (that is, Acrobat itself) rather than to the document, and in fact your script(s) can only run outside of document scope: the relevant API methods are prevented (by security restrictions) from executing as part of a document. So you can't just attach scripts to a PDF document's form fields, say, and expect to do AJAX. Instead, you have to put scripts in a /Javascripts folder on your local drive, under your /Acrobat install path. Acrobat registers the scripts on program startup, and they remain in scope for the duration of an Acrobat session (regardless of how many documents you open). In this sense, you can think of an Acrobat AJAX script as being similar to, say, a Jetpack script in Firefox.

    You may be wondering what, then, is the user gesture for getting a so-called folder-level script to fire? In Acrobat, the standard pattern here is to expose a folder-level script as a new menu item. The Acrobat JavaScript API has a method, app.addMenuItem, that looks like this:

    app.addMenuItem({

       cName: "Save Annotations to CRX",
       cParent: "File",
           nPos: 0
       cExec: 'myMethod();',
       cEnable: "event.rc = (event.target != null);",
    });

    The method takes a parameter block that can have several (mostly optional) properties. The cName property is the name of the new menu item. The cParent property designates the Acrobat menu in which the new menu item should live (in this case, the File menu), while nPos indicates the desired position of the new menu command in the list of commands on the menu in question. The cExec property points to the custom code you want to execute when the menu item is selected by the user.

    The optional cEnable property lets you specify whether the new menu item is enabled when the user sees it, based on certain conditions. In the example shown above, we've got code that essentially tests whether a document is already open in Acrobat. If no document is open, the menu command is greyed out.

    In ordinary (browser) AJAX, you're no doubt accustomed to using the XMLHttpRequest object to do the heavy lifting. Acrobat has its own XHR construct, called Net.HTTP.request. Like the addMenuItem() method above, the request() method of Net.HTTP takes a parameter block as an argument. There are many possible properties you can supply on this parameter block object (and they're all documented in the JavaScript for Acrobat API Reference). For our purposes, the most important are cVerb (which can be "GET", "POST", or any number of other HTTP and/or WebDAV verbs), cURL (the URL to which the request should be sent), aHeaders (a place to specify the HTTP request headers for this transmission), oRequest (the data stream for the POST), and the all-important oHandler. The latter needs to point to an object (any object) that has a response() method. The response() method of the object is called when the server is done handling your request. In other words, it's your callback method, analogous to onreadystatechange in conventional AJAX.

    When the response method of your handler is called, it gets called with four arguments. The first argument is a reference to the response body (a stream object). The second argument is just the URL to which the request was sent. The third argument points to an exception object (if the request was not successful). The fourth argument is an array of response headers returned from the server. Again, all of this is documented in Adobe's JavaScript for Acrobat API Reference and I won't belabor any of it further here.

    The code below shows an example of what you can do using Acrobat AJAX. In this example, we harvest all of the annotations (if any) in the currently open PDF document (the frontmost document, if there are multiple docs open), convert those annotations to XML, and POST the XML to CRX at a location of http://localhost:7402/content/acrobat/annots/, under a node name of "Annots for [PDFname]" (where PDFname is the file name of the PDF document from which annotations were taken).

    AjaxRequest = function(cURL) {
            this.params =
            {
                    cVerb: "POST", // default
                    cURL: cURL,
                    aHeaders: [
                    { name: "Content-Type",
                            value: "application/x-www-form-urlencoded"
                    }
                    ],

                    oRequest: null,

                    oHandler:
                    {
                            response: function(msg, uri, e,h){
                                    var stream = msg;
                                    var string = "";
                                    string = SOAP.stringFromStream( stream );
                                    app.alert( string );
                            }
                    }
            };

            this.invoke = function( ) {

                    Net.HTTP.request(this.params);
            }
    }

    // Prepare an AjaxRequest
    function createRequest( annots, fileName, url ) {

            var ajax = new AjaxRequest( );

            // where to store data in CRX:
            ajax.params.cURL = url;

            ajax.params.cVerb = "POST";

            var data = {};

            // optional redirect:
            data[":redirect"] =
            "http://localhost:7402/content/acrobat/thankyou.txt";

            // this is the name of the new node
            data[":nameHint"] = "Annots for " + fileName;

            // this is our annotation data as XML
            data.data = getAnnotationsAsXML( annots );

            var dataString = createDataString( data );

            // convert our data to a stream object
            ajax.params.oRequest =
            Net.streamFromString( dataString );

            return ajax;
    }

    // create a string of the form
    // name1=value1&name2=value2 [etc]
    function createDataString( object ) {

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

            return data.join( "&" );
    }

    // converts annots to XML using E4X
    function getAnnotationsAsXML( annots ) {

            var xmlOutput = <annots></annots>;
            for ( var i = 0; i < annots.length; i++ )
            {
                    var props = annots[i].getProps();
                    xmlOutput.* += <annot/>;
                    var parent = xmlOutput.annot[i];
                    parent.* = <author>{props.author}</author>;
                    parent.* += <contents>{props.contents}</contents>;
                    parent.* += <page>{props.page}</page>;
                    parent.* += <creationDate>{props.creationDate}</creationDate>;
                    parent.* += <type>{props.type}</type>;
            }

            return xmlOutput.toXMLString();
    }


    var theURL = "http://localhost:7402/content/acrobat/annots/";

    // Add a new menu item under File
    app.addMenuItem({
            cName: "Save Annotations to CRX",
            cParent: "File",
            cExec: 'request = createRequest( this.getAnnots( ), this.documentFileName, theURL ); request.invoke();',
            cEnable: "event.rc = (event.target != null);",
            nPos: 0
    });
     

    If you have a copy of Acrobat Professional, copy and paste the above code to a text file (with an extension of .js) in your /Javascripts folder under your /Acrobat path, then restart Acrobat and you should see a new menu command appear under the File menu, called "Save Annotations to CRX."

    Note that to prevent security errors, you may have to go into Preferences (Control-K) and turn off Enhanced Security, or else add the currently open PDF document to the list of trusted docs. (In the preferences dialog, choose Security (Enhanced) in the list on the left.)

    Acrobat's JavaScript API makes it trivially easy to harvest all annotations from a PDF document with a single line of code:

    this.getAnnots()

    What you get back from this call is an array of Annotation objects (see Adobe's documentation), each of which has numerous properties that can be parsed out. We convert the annotations and properties to XML in the following method:

    // converts annots to XML using E4X

    function getAnnotationsAsXML( annots ) {

            var xmlOutput = <annots></annots>;
            for ( var i = 0; i < annots.length; i++ )
            {
                    var props =     annots[i].getProps();
                    xmlOutput.* += <annot/>;
                    var parent = xmlOutput.annot[i];
                    parent.* = <author>{props.author}</author>;
                    parent.* += <contents>{props.contents}</contents>;
                    parent.* += <page>{props.page}</page>;
                    parent.* += <creationDate>{props.creationDate}</creationDate>;
                    parent.* += <type>{props.type}</type>;
            }

            return xmlOutput.toXMLString();
    }

    You'll notice we use E4X syntax here for building the XML. If you're not familiar with it, E4X (ECMAScript extensions for XML, otherwise known as ECMA-357) constitutes a powerful -- and quite handy -- syntax for manipulating XML in ECMAScript. It is supported not only in Acrobat JavaScript but (on the server side) in Sling as well.

    When I ran this script on an annotated PDF document of my own, I got XML that looked like this:

    <annots>
      <annot>
        <author>Admin</author>
        <contents>Need more discussion of "privileges"</contents>
        <page>31</page>
        <creationDate>Mon Jul 12 2010 08:03:17 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Underline</type>
      </annot>
      <annot>
        <author>Admin</author>
        <contents>Not sure we need to have this sentence.</contents>
        <page>29</page>
        <creationDate>Mon Jul 12 2010 08:02:39 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Highlight</type>
      </annot>
      <annot>
        <author>Admin</author>
        <contents>Is this the correct copyright date?</contents>
        <page>1</page>
        <creationDate>Mon Jul 12 2010 08:01:44 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Highlight</type>
      </annot>
      <annot>
        <author>Admin</author>
        <contents>We need to strike this.</contents>
        <page>729</page>
        <creationDate>Tue Jul 06 2010 14:43:57 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Highlight</type>
      </annot>
      <annot>
        <author>Admin</author>
        <contents>I am underlining this.</contents>
        <page>729</page>
        <creationDate>Tue Jul 06 2010 14:44:12 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Underline</type>
      </annot>
      <annot>
        <author>Admin</author>
        <contents>I liked this.</contents>
        <page>57</page>
        <creationDate>Tue Jul 06 2010 15:04:21 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Highlight</type>
      </annot>
      <annot>
        <author>Admin</author>
        <contents>This does not seem right.</contents>
        <page>57</page>
        <creationDate>Tue Jul 06 2010 15:04:32 GMT-0400 (Eastern Daylight Time)</creationDate>
        <type>Text</type>
      </annot>
    </annots>

     

    This is what gets stored in CRX.

    A while ago, I said that AJAX scripts in Acrobat are scoped to the application and may not (for security reasons) run in the context of a given document. Given that this is so, you may be wondering, at this point, how it is that we can harvest annotations from a document programmatically in an AJAX script. The key is that our script doesn't fire until there's actually a document open in Acrobat (remember, the menu command is dimmed out if there's no PDF open). When the script does finally run, it's safe to call this.getAnnots() -- "this" will be a reference to the currently open document (and using it has no security side-effects). The only real restriction is that you can't call Net.HTTP.request() from a document-level script. Doing so will cause Acrobat to complain.

    Those are the basics of doing AJAX against a CRX repository from Acrobat. In a future blog, I'll show how to populate a PDF form with values slurped from CRX (and do it in a way that allows you to use Acrobat Reader rather than Acrobat Professional). Stay tuned!

    Posted by Michael Marth MAY 23, 2008

    Posted in http, rest, sling and tutorial Comments 8

    Back in November last year I wrote a post about Sling's request processing. Things have changed quite a bit till then so here's a revised version.

    In Apache Sling each (http) request is mapped onto a JCR resource, i.e. a repository node. This is very different from other web frameworks you might be familiar with, say, Struts or Rails. In these frameworks a request is mapped onto a controller, i.e. a url really addresses application code, so the application developer usually implements some application logic that retrieves model data and passes it on to the view.

    Just like Rails or Struts, Sling implements a model-view-controller architecture. However, in Sling a request addresses a piece of content. The mapping between request and model (data, content) is accomplished through the url so there is no need for further custom mapping logic.

    Node selection

    So, how does this work in detail? Consider an http GET request for the url:

    /content/corporate/jobs/developer.html

    First, Sling will look in the repository for a file located at exactly this location. If such a file is found, it will be streamed into the response as is. This behavior allows you to use Sling as a web server and store your web application's binary data in the repository as well.

    However, if there is no file to be found Sling will look for a repository node located at:

    /content/corporate/jobs/developer

    (i.e. it drops the file extension). If this node cannot be found Sling will return the http code 404.

    Script folders

    The scripts that Sling uses to process http requests are stored in subfolders of "/apps". Those subfolders are usually of type nt:folder, but that's not a requirement.

    Script selection

    Nodes can have a special property named "sling:resourceType" that determines the resource type. Let us consider the simplest case (using the example request URL from above) and assume that the resource type is, say, "hr/job". The selected script will then be "/apps/hr/job/job.esp" (the last part of the resource type will have to be the file name). This works for GET requests and URLs ending in ".html".

    Requests using other request methods, say POST, will cause Sling to look for the script at "/apps/hr/job/job.POST.esp". Request URLs ending in something else than ".html", say ".pdf", will make Sling look at "/apps/hr/job/job.pdf.esp". The convention to distinguish the two cases is that http methods are all uppercase and the extension of the request is all lowercase.

    In a content-centric application the same content (aka nodes) must often be displayed in different variations, e.g. as a teaser view and as a detail view. In Sling this is achieved through selectors. The selector is specified in the URL like e.g.

    /content/corporate/jobs/developer.detail.html

    For this URL Sling would locate the script at "/apps/hr/job/job.detail.esp"

    If the selected resource has no special resource type a script will be looked up based on the content path. For example, the script for /content/corporate/jobs.html will be searched in /apps/corporate.

    Script engine

    The ".esp" extension of the scripts used in the examples above indicates script engine to use. ".esp" stands for Ecma script and internally uses Rhino, Mozilla's Javascript engine. Other supported extensions are ".rb" for JRuby scripts, ".jsp" for JSPs or ".jst" for client-side execution (".jst" denotes Javascript template).

    Some interesting special cases

    The examples above describe rendering nodes as html or as a pdf. Howevere, there are also some built-in renderers for json and txt. The corresponding node presentations are located at (using the example form above):

    /content/corporate/jobs/developer.json

    and

    /content/corporate/jobs/developer.txt

    respectively.

    For http error handling (404 or 500) Sling will look for a script at "/apps/sling/servlet/errorhandler/404.esp" and 500.esp, respectively.

    More on script selection

    If you need to find out more on the details of script selection in Sling have a look at Sling ticket 387 where developer Felix Meschberger a lot more on the script resolution process.