You are here

Creating Alfresco Share sites with JavaScript

Martin Bergljung's picture
Martin Bergljung

The first article I wrote about creating Alfresco Share sites with JavaScript has been the most viewed of all my articles in the blog. It has a problem though, the sites you create with the script(s) described in the article does not live over server re-starts. This is because the required Site configuration in the AVM store is never created correctly. So I thought I’d give it a go and try and create a new script that is creating the sites properly. Robin Bramley, a colleague of mine, also pointed out that the solution would be more flexible and extensible if the site data for the sites that should to be created by the script were described in JSON. So here we go again...

Source code for this article can be downloaded from here.

Introduction

Some companies or organisations implementing Alfresco Share solutions have more than a couple of Alfresco Share sites that they need to create and manage. In the beginning it is quite natural to create the sites manually via the Alfresco Share UI. However, when the number of sites reaches into the hundreds, and each site has a complex folder hierarchy and many different permission settings etc, then it might make sense to try and manage all the site creation and setup via scripting.

Handling site creation and setup via scripts is also going to be very helpful when you setup the system in different environments (i.e. development, staging, production etc). New developers joining the project will also be able to easily setup the development environment by just running a few scripts.

In the first edition of this article I explored setting up sites with the Alfresco JavaScript API. The API has a method called createSite on the siteService that can be used to create a site, but it does not take you all the way. We had to also run a Web Script that would setup site preset in the Spring Surf sitedata object. This worked fine as long as the Alfresco Tomcat server was not restarted, in which case the site would not be accessible. This was because the necessary content in the avm://sitestore had not been created as in the following picture:

The content in the picture above can be accessed via the Node Browser and a path similar to:

avm://sitestore/-1|alfresco|site-data /{http://www.alfresco.org/model/content/1.0}alfresco/{http://www.alfresco.org/model/content/1.0}site-data/{http://www.alfresco.org/model/content/1.0}components

I am running the examples in this article on Alfresco Community 3.4d. So I had to figure out some other way to create the sites then just using the JavaScript API alone.

Creating sites with curl from the command line

When trying to figure out how to create Alfresco Share sites via scripting I had a look at how the Share web application does it. I used HttpFox (a Firefox development tool) to investigate what takes place when you create a site from the Share UI.

The following URL and Headers are sent:

So we can see that the URL that is used is http://localhost:8080/share/service/modules/create-site and it is a POST of content of type JSON. The JSON content specifies data for the site we want to create and it looks like this:

{
     "visibility" : "PUBLIC",
     "title" : "Test",
     "shortName" : "test",
     "description" : "Testing",
     "sitePreset" : "site-dashboard"
}

See previous article for an explanation of these properties.

There are also authentication headers alfLogin, JESSIONID, and alfUsername2alfLogin and alfUsername2 cookies are Alfresco specific and are not necessary when creating sites.

The question now was - where do we get the JSESSIONID cookie from? It turns out you can get it from the Share login request.

The login request looks like this in HttpFox:

The login request URL is http://localhost:8080/share/page/dologin and it too is a POST request with the username and password as data. The response contains the needed JSESSIONID Cookie. So now we can easily create new sites from the command line, with for example curl, and they will persist after a server restart.

The following example illustrates, first we login to get needed cookie:

X:\tools\curl7.21>curl -v -d @login.txt -H "Content-Type:application/x-www-form-urlencoded" http://localhost:8080/share/page/dologin

* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /share/page/dologin HTTP/1.1
> User-Agent: curl/7.21.1 (i386-pc-win32) libcurl/7.21.1 OpenSSL/0.9.8o zlib/1.2.5
> Host: localhost:8080
> Accept: */*
> Content-Type:application/x-www-form-urlencoded
> Content-Length: 29
> 
< HTTP/1.1 302 Moved Temporarily
< Server: Apache-Coyote/1.1
< Set-Cookie: JSESSIONID=E12C8523A8A8D19BEBD68FE84FDB2170; Path=/share
< Set-Cookie: alfLogin=1327935313; Expires=Mon, 06-Feb-2012 14:55:13 GMT; Path=/share
< Set-Cookie: alfUsername2="YWRtaW4="; Version=1; Max-Age=604800; Expires=Mon, 06-Feb-2012 14:55:13 GMT; Path=/share
< Location: http://localhost:8080/share
< Content-Length: 0
< Date: Mon, 30 Jan 2012 14:55:13 GMT
< 
* Connection #0 to host localhost left intact
* Closing connection #0

In this call I logged in as admin by supplying the login.txt username=admin&password=admin

We now got a JSESSIONID that we need for our call to create the site. The POST data, specified in JSON format, will look like this and be kept in a file called site_data.json:

{
     "visibility" : "PUBLIC",
     "title" : "My Test Site",
     "shortName" : "mytestsite",
     "description" : "My Test Site created from command line",
     "sitePreset" : "site-dashboard"
}

The site is then created like this:

X:\tools\curl7.21>curl -v -d @site_data.json -H "Cookie:JSESSIONID=E12C8523A8A8D19BEBD68FE84FDB2170" -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/share/service/modules/create-site

* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /share/service/modules/create-site HTTP/1.1
> User-Agent: curl/7.21.1 (i386-pc-win32) libcurl/7.21.1 OpenSSL/0.9.8o zlib/1.2.5
> Host: localhost:8080
> Cookie:JSESSIONID=E12C8523A8A8D19BEBD68FE84FDB2170
> Content-Type:application/json
> Accept:application/json
> Content-Length: 171
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Cache-Control: no-cache
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Content-Language: en-GB
< Transfer-Encoding: chunked
< Date: Mon, 30 Jan 2012 15:01:48 GMT
< 
{
   "success": true

}* Connection #0 to host localhost left intact
* Closing connection #0

So we can now create sites via scripting as long as we can make HTTP POST requests from the scripting language and manage JSON. If this is all you need then you can pretty much stop here, if you want to do more then read on...

Creating a Share site with a custom Web Script

So all we have to do now is just create a Web Script that makes the above POST requests and we are in business. After quite some time I came to the conclusion that doing these POST requests in a JavaScript controller does not work (believe me, I did a lot of trial and error with both Alfresco Repo Web Scripts and Spring Surf Web Scripts). The solution I came up with is an Alfresco Web Script that uses both a Java Controller and a JavaScript controller. The Java controller makes the two POST requests to create the site and the JavaScript controller sets up the rest of the site such as members and folders.

SITE DATA FORMAT

For this new Web Script I also wanted to implement a more efficient and flexible way of specifying all the information for a site that should be created. This would include the basic site information such as short name, title, description, visibility etc, together with what members should be added to each site and what folder structure should be created for each site. There should also be the possibility to specify more stuff, such as for example data lists, without requiring too much coding or changes. To specify all data for a site I choose to use a JSON structure as it is very flexible, extensive, and works well with JavaScript. The following example shows how it is defined and built up:

{
    "sites" : [
        {
            "visibility" : "PUBLIC",
            "title" : "My Test Site100",
            "shortName" : "mytestsite100",
            "description" : "My Test Site created from command line 100",
            "sitePreset" : "site-dashboard",
            "members" : [
                {
                    "userName" : "martin",
                    "role" : "SiteConsumer"
                },
                {
                    "userName" : "veronika",
                    "role" : "SiteContributor"
                }
            ],
            "folders" : [
                {
                    "name" : "test",
                    "title" : "testTitle",
                    "description" : "testDesc",
                    "subfolders" : [
                        {
                            "name" : "subtest",
                            "title" : "subtestTitle",
                            "description" : "subtestDesc",
                            "subfolders" : [
                                {
                                    "name" : "subsubtest",
                                    "title" : "subsubtestTitle",
                                    "description" : "subsubtestDesc"
                                },
                                {
                                    "name" : "subsubtest2",
                                    "title" : "subsubtest2Title",
                                    "description" : "subsubtest2Desc"
                                }
                            ]
                        },
                        {
                            "name" : "subtest2",
                            "title" : "subtest2Title",
                            "description" : "subtest2Desc"
                        }
                    ]
                },
                {
                    "name" : "test2",
                    "title" : "test2Title",
                    "description" : "test2Desc"
                }
            ]

        },
        {
            "visibility" : "PUBLIC",
            "title" : "My Test Site101",
            "shortName" : "mytestsite101",
            "description" : "My Test Site created from command line 101",
            "sitePreset" : "site-dashboard"
        }
    ]
}

There is a top level property called sites that is an array of all the sites that should be created by the Web Script. Each site entry contains the basic information about the site plus the members that should be added to the site (in the form of an array) and the folders that should be created in the site’s Document Library (also in the form of an array). The folder structure can be specified in any depth by using more nested subfolders properties as demonstrated above.

This JSON structure is then just POSTed to the new site creation Web Script, which parses it and creates the sites based on the data. Each site entry in the JSON structure is also POSTed as is to the /service/modules/create-site Web Script via the Java controller as it has the required properties.

Using curl to call this new Web Script, which will be called createSites by the way, with the above JSON structure stored in a file called site_data.json, would look like this:


X:\tools\curl7.21>curl -v -u admin:admin -d @site_data.json -H "Content-Type:application/json" "http://localhost:8080/alfresco/service/mycompany/createSites"
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'admin'
> POST /alfresco/service/mycompany/createSites HTTP/1.1
> Authorization: Basic YWRtaW46YWRtaW4=
> User-Agent: curl/7.21.1 (i386-pc-win32) libcurl/7.21.1 OpenSSL/0.9.8o zlib/1.2.5
> Host: localhost:8080
> Accept: */*
> Content-Type:application/json
> Content-Length: 1306
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Set-Cookie: JSESSIONID=70F2ADAED45119455DAB60D7D453E31A; Path=/alfresco
< Cache-Control: no-cache
< Pragma: no-cache
< Content-Type: text/html;charset=UTF-8
< Content-Length: 994
< Date: Mon, 05 Mar 2012 11:11:11 GMT
< 
        Created site (My Test Site100[mytestsite100]) 

        Added user (martin) as (SiteConsumer) in site (My Test Site100 [mytestsite100]) 

        Added user (veronika) as (SiteContributor) in site (My Test Site100 [mytestsite100]) 

        Document Library not found (documentLibrary), created it under (/Company Home/Sites/mytestsite100) for site (mytestsite100) 

        Added folder (test) under (/Company Home/Sites/mytestsite100/documentLibrary) 

        Added folder (subtest) under (/Company Home/Sites/mytestsite100/documentLibrary/test) 

        Added folder (subsubtest) under (/Company Home/Sites/mytestsite100/documentLibrary/test/subtest) 

        Added folder (subsubtest2) under (/Company Home/Sites/mytestsite100/documentLibrary/test/subtest) 

        Added folder (subtest2) under (/Company Home/Sites/mytestsite100/documentLibrary/test) 

        Added folder (test2) under (/Company Home/Sites/mytestsite100/documentLibrary) 

        Created site (My Test Site101[mytestsite101]) 

* Connection #0 to host localhost left intact
* Closing connection #0

Now, let’s get on with coding the Web Script.

WEB SCRIPT DESCRIPTOR

First thing we need to do is create a descriptor for the Web Script as follows:


  Create Site(s)
  Creates one or more sites as specified in past in POST JSON object
  To test: curl -v -u admin:admin -d @site_data.json -H "Content-Type:application/json" "http://localhost:8080/alfresco/service/mycompany/createSites"
  
  /mycompany/createSites
  user
  required

Put the descriptor in a file called createSites.post.desc.xml. In my case I have stored the descriptor file in the trunk\_alfresco\config\alfresco\extension\templates\webscripts\com\mycompany\sites directory in my build project. You can store the file in any directory structure you like under /templates/webscripts.

For more information about the build project I use see my previous blog, Setting up a build project for Alfresco Explorer, Share, and WQS extensions.

WEB SCRIPT JAVA CONTROLLER

Next up is the Java controller for the Web Script and it will do the main job of creating the sites by first calling the /page/dologin URL to get a JSESSION id and then call the /service/modules/create-site URL for each site that should be created.

The first part of the Java class looks as follows and defines a bunch of constants that we will need and some setters for the Alfresco site service and the username and password to be used when logging in with the /page/dologin URL:

public class CreateSitesWebScript extends DeclarativeWebScript {
    private static Log logger = LogFactory.getLog(CreateSitesWebScript.class);

    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain";
    private static final String CONTENT_TYPE_JSON = "application/json";
    private static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
    private static final String UTF_8 = "UTF-8";
    private static final String BASE_URL = "http://localhost:8080/share";
    private static final String LOGIN_URL = BASE_URL + "/page/dologin";
    private static final String CREATE_SITE_URL = BASE_URL + "/service/modules/create-site";

    private SiteService siteService;

    private String alfrescoUsername = "admin";
    private String alfrescoPwd = "admin";

    public void setSiteService(SiteService siteService) {
        this.siteService = siteService;
    }

    public void setAlfrescoUsername(String alfrescoUsername) {
        this.alfrescoUsername = alfrescoUsername;
    }

    public void setAlfrescoPwd(String alfrescoPwd) {
        this.alfrescoPwd = alfrescoPwd;
    }

Next method is the one that will be called by the Web Script engine and it will setup the sites using the following code, comments inline:

@Override
    protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) {
        // Extract the information for the sites that should be created from JSON POST
        Content jsonContent = req.getContent();
        if (jsonContent == null) {
            throw new WebScriptException(Status.STATUS_BAD_REQUEST,
                    "Missing POST body with data for creating site(s).");
        }

        // Create Apache HTTP Client to use for both calls
        HttpClient httpClient = new HttpClient();

        // Login to Share to get a JSESSIONID setup in HTTP Client
        String loginData = "username=" + alfrescoUsername + "&password=" + alfrescoPwd;
        makePostCall(httpClient, LOGIN_URL, loginData, CONTENT_TYPE_FORM, "Login to Alfresco Share",
                HttpStatus.SC_MOVED_TEMPORARILY);

        // Extract site information from JSON object and create the sites, if they do not already exists
        JSONObject json = null;
        try {
            json = new JSONObject(jsonContent.getContent());
            JSONArray sites = json.getJSONArray("sites");

            for (int i = 0; i < sites.length(); i++) {
                JSONObject site = (JSONObject) sites.get(i);

                // Get the site's short name (URL)
                String shortName = site.getString("shortName");

                SiteInfo siteInfo = siteService.getSite(shortName);
                if (siteInfo != null) {
                    // Site already exists, cannot create it, continue with next one
                    logger.warn("Site (" + shortName + ") already exists, cannot create it");
                    continue;
                }

                makePostCall(httpClient, CREATE_SITE_URL, site.toString(), CONTENT_TYPE_JSON,
                        "Create site with name: " + shortName, HttpStatus.SC_OK);
            }
        } catch (JSONException jErr) {
            throw new WebScriptException(Status.STATUS_BAD_REQUEST,
                    "Unable to parse JSON POST body: " + jErr.getMessage());
        } catch (IOException ioErr) {
            throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR,
                    "Unable to retrieve POST body: " + ioErr.getMessage());
        }

        return new HashMap(); // empty model
    }

The makePostCall is a private custom method that makes the actual HTTP POST call and it looks as follows:

private void makePostCall(HttpClient httpClient, String url, String data, String dataType,
                              String callName, int expectedStatus) {
        PostMethod postMethod = null;
        try {
            postMethod = createPostMethod(url, data, dataType);
            int status = httpClient.executeMethod(postMethod);

            if (logger.isDebugEnabled()) {
                logger.debug(callName + " returned status: " + status);
            }

            if (status == expectedStatus) {
                if (logger.isDebugEnabled()) {
                    logger.debug(callName + " with user " + alfrescoUsername);
                }
            } else {
                logger.error("Could not " + callName + ", HTTP Status code : " + status);
            }
        } catch (HttpException he) {
            logger.error("Failed to " + callName, he);
        } catch (AuthenticationFault ae) {
            logger.error("Failed to " + callName, ae);
        } catch (IOException ioe) {
            logger.error("Failed to " + callName, ioe);
        } finally {
            postMethod.releaseConnection();
        }
    }

It uses the Apache HTTP Client to setup and make the call. This method also uses another private custom method called createPostMethod that looks like this:

private PostMethod createPostMethod(String url, String body, String contentType)
            throws UnsupportedEncodingException {
        PostMethod postMethod = new PostMethod(url);
        postMethod.setRequestHeader(HEADER_CONTENT_TYPE, contentType);
        postMethod.setRequestEntity(new StringRequestEntity(body, CONTENT_TYPE_TEXT_PLAIN, UTF_8));

        return postMethod;
    }

This is all the code for the Java controller. However, it needs to be registered as a Spring bean to be recognized as part of the new Web Script. This is how this is done:

<bean id="webscript.com.mycompany.sites.createSites.post"
          class="com.mycompany.cms.webscript.CreateSitesWebScript" parent="webscript">
        <property name="siteService">
            <ref bean="siteService"/>
        </property>
        <property name="alfrescoUsername" value="admin" />
        <property name="alfrescoPwd" value="admin" />
    </bean>

The important bit is the bean id which has to start with webscript and has to end in createSites.post as the Web Script id is createSites and it is called with a POST method.

So we now got the sites created with proper content in the AVM store etc. We can now continue and process them in the JavaScript controller.

WEB SCRIPT JAVASCRIPT CONTROLLER

The JavaScript controller for the Web Script will handle setting up the site members, folders, and any other data that we would like to pre-populate the new site with. It’s easy to manipulate and read JSON in JavaScript so we should do as much as possible of the site manipulation in JavaScript code.

Here is how it looks like; it starts off with the JavaScript code that will be executed immediately when the controller is called:

var activityLog = [];

// Evaluate passed in JSON string to JavaScript object
var requestObject = null;
if (requestbody.mimetype == "application/json") {
    requestObject = eval('(' + requestbody.content + ')');
} else {
    log("Was expecting to find JSON in the request body, instead there was: " + requestbody.mimetype);
}

// Loop through all sites that were created by the Java controller
// and create any extra folders and add any extra site members
var sites = requestObject.sites;
for (var i = 0; i < sites.length; i++) {
    var site = sites[i];
    var siteInfo = checkSite(site.shortName);
    if (siteInfo != null) {
        // Add members if we got any
        if (site.members != null) {
            for (var j = 0; j < site.members.length; j++) {
                var member = site.members[j];
                addSiteMember(siteInfo, member.userName, member.role);
            }
        }

        // Add folders if we got any
        if (site.folders != null) {
            var docLibFolder = getDocLibFolder(siteInfo);
            if (docLibFolder != null) {
                for (var k = 0; k < site.folders.length; k++) {
                    var folderInfo = site.folders[k];
                    var newFolder = addFolder(docLibFolder, folderInfo);
                    addSubFolders(newFolder, folderInfo);
                }
            }
        }
    }
}

model.activityLog = activityLog;

The first line defines an array of log texts that will be passed on to the template that will be part of this Web Scripts. Then we evaluate the JSON structure that was POSTed to the Web Script, we can get to it via the requestBody.content property in the same way we could in the Java controller (i.e. req.getContent()). We convert the JSON text to a JavaScript object so we can have easy access to all properties and arrays in the JSON structure.

Note. Any model that we setup in the Java controller is not accessible in the JavaScript controller, don’t know why...

We then loop through all sites defined in the sites array, accessed via the requestObject.sites array. We check that each site was really created by the Java controller, and exists, by using the custom checkSite function. We then setup the members of the site by navigating into the JSON structure via the site.members array and then calling the custom addSiteMember function for each user that should be added as a member of the site.

When we are done adding members we setup the folder structure in the Document Library by accessing the site.folders array. We start by getting the Document Library folder itself by calling the custom getDocLibFolder function. For each folder we call the custom addFolder function, which creates it, and then we call the addSubFolders custom function, which recursively creates all subfolders to the top folder in the Document Library.

Now let’s have a look at the custom JavaScript functions that were used. The first one is the checkSite and it looks as follows:

function checkSite(shortName) {
    var newSite = siteService.getSite(shortName);
    if (newSite != null) {
        log("Created site (" + newSite.title + "[" + newSite.shortName + "])");
    } else {
        log("Site (" + shortName + ") was not created, will not create folders or add members to site");
    }
    return newSite;
}

Here we can see that we use the Site Service to check if the site exists, we can do this as we are executing inside an Alfresco Repo Web Script, and not a Spring Surf Web Script. Next function that is used is the addSiteMember:

function addSiteMember(site, userName, siteRole) {
    var siteName = site.title + " [" + site.shortName + "]";
    if (!site.isMember(userName)) {
        site.setMembership(userName, siteRole);
        log("Added user (" + userName + ") as (" + siteRole + ") in site (" + siteName + ")");
    } else {
        log("Did not add user (" + userName + ") to site (" + siteName + "), user is already a member");
    }
}

It also makes use of the Site Service to add a user as a member of a site with a particular role. The valid roles to choose from are SiteConsumer, SiteManager, SiteContributor, and SiteCollaborator.

The getDocLibFolder function looks like this:

function getDocLibFolder(site) {
    var componentName = "documentLibrary";

    // Have to use Lucene to find the newly created site
    var siteNodes = search.luceneSearch('+PATH:"/app:company_home/st:sites/cm:' + site.shortName + '"');
    var siteNode = null;
    if (siteNodes != null && siteNodes.length > 0) {
        siteNode = siteNodes[0];
    } else {
        log("Unable to create container (" + componentName + ") for site (" + site.shortName +
              "). Could not find site folder (i.e. /Sites/" + site.shortName + ")");
        return null;
    }

    var docLibNode = siteNode.childByNamePath(componentName);
    if (docLibNode == null) {
        docLibNode = siteNode.createNode(componentName, "cm:folder");
        if (docLibNode == null) {
            log("Unable to create container (" + componentName + ") for site (" + site.shortName +
                    "). (No write permission?)");
        } else {
            var docLibProps = new Array(1);
            docLibProps["st:componentId"] = componentName;
            docLibNode.addAspect("st:siteContainer", docLibProps);
            docLibNode.save();

            log("Document Library not found (" + componentName +
                    "), created it under (" + docLibNode.displayPath + ") for site (" + site.shortName + ")");
        }
    }

    return docLibNode;
}

Here we start with a Lucene search to find the site folder under /Company Home/Sites. I could only find the site folder by using a Lucene search, neither the companyhome.childByNamePath function nor the site.getContainer function worked to get to the Document Library folder, do not why – maybe something to do with the site being created in the Java controller that is part of the same Web Script. The Document Library folder is created if we don’t find it.

When we have the Document Library folder we can start creating the folders under it as per the JSON structure.

The addFolder method is used to create a folder and it looks like this:

function addFolder(parentFolder, folderInfo) {
    var newFolder = parentFolder.childByNamePath(folderInfo.name);
    if (newFolder == null) {
        newFolder = parentFolder.createFolder(folderInfo.name);

        newFolder.properties["cm:title"] = folderInfo.title;
        newFolder.properties["cm:description"] = folderInfo.description;
        newFolder.save();

        log("Added folder (" + folderInfo.name + ") under (" + newFolder.displayPath + ")");
    } else {
        log("Did not add folder (" + folderInfo.name + ") under (" + newFolder.displayPath + "), it already exists");
    }

    return newFolder;
}

This method first check so the folder does not already exists and then it creates it. When it is created the title and description is also set.

The last method used is the one that sets up subfolders for a top level folder, it is called addSubFolders and here is the code for it:

function addSubFolders(parentFolder, folderInfo) {
    if (folderInfo.subfolders != null) {
        for (var i = 0; i < folderInfo.subfolders.length; i++) {
            var subFolderInfo = folderInfo.subfolders[i];
            var newSubFolder = addFolder(parentFolder, subFolderInfo);
            addSubFolders(newSubFolder, subFolderInfo);
        }
    }
}

It starts off by checking if this folder has any subfolders defined in the JSON structure, this is done with the folderInfo.subfolders != null check. Any subfolders that are found are then created with addFolder function and then we call ourselves again recursively to create more sub folders, if there are any.

That is all there is to it for the JavaScript controller and it is saved in a file called createSites.post.js. Last thing we need to add to the Web Script for it to be complete is a template.

WEB SCRIPT TEMPLATE

Create a FreeMarker template as the view with the following code:


<#list activityLog as log>
        ${log} 


Save the FreeMarker view code in a file called createSites.post.html.ftl.

Adding more stuff to the site than just Members and Folders

Now that we got a very flexible JSON structure to our disposal when specifying the site data we could easily add more things to it. For example, let’s say we wanted a Data List to be pre-populated with a number of items in one of our new sites. We could easily do this by extending the JSON structure and adding some code to the JavaScript controller.

The JSON structure could be extended as in the following example:


{
    "sites" : [
        {
            "visibility" : "PUBLIC",
            "title" : "My Test Site100",
            "shortName" : "mytestsite100",
            "description" : "My Test Site created from command line 100",
            "sitePreset" : "site-dashboard",
            "members" : [...],
            "folders" : [...],

            "datalists" : [...]

        },
        {
            "visibility" : "PUBLIC",
            "title" : "My Test Site101",
            "shortName" : "mytestsite101",
            "description" : "My Test Site created from command line 101",
            "sitePreset" : "site-dashboard"
        }
    ]
}

Here we define a new property called datalists, which can contain any number of datalist definitions in a format of your liking. We can then access it in the JavaScript controller code as follows:


var sites = requestObject.sites;
for (var i = 0; i < sites.length; i++) {
    var site = sites[i];
    var siteInfo = checkSite(site.shortName);
    if (siteInfo != null) {
        // Add members if we got any
        . . .

        // Add folders if we got any
        . . .

        // Add datalists if we got any
        if (site.datalists != null) {
            for (var m = 0; m < site.datalists.length; m++) {
                var datalist = site.datalists[m];
                . . .
            }
        }

    }
}

For more information about how to create datalists via JavaScript have a look at this blog.

Debugging Web Scripts

Now if you are building and working with this Web Script you are probably at one point or another going to need to do some debugging. At least I had to...☺

DEBUGGING THE JAVA CONTROLLER

Debugging Java code is easy and you are probably quite used to that. Look in your development environment and see how it likes to attach to the JVM for debugging. I use IntelliJ IDEA and attach to the JVM remotely via port 5005. I get the JVM to listen to it by using the following Tomcat setting in my windows environment:

CATALINA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005

This works if I start-up Alfresco Tomcat from command line and not as a Windows Service. If it is started as a Windows Service then I need to make some settings to it by accessing the service properties as follows:

X:\Alfresco3.4dBlogg\tomcat\bin>tomcat6w.exe //ES//alfrescoTomcatnum

This opens up a dialog for changing Windows Service properties. Click on the Java tab and add the following properties for enabling debugging:

-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005

DEBUGGING THE JAVASCRIPT CONTROLLER

Now, debugging the JavaScript controller is not as easy as debugging the Java controller. First of all, you cannot debug the JavaScript controller if Alfresco Tomcat runs as a service. So start Alfresco Tomcat as follows (MySQL can still run as a service):

X:\Alfresco3.4dBlogg\tomcat\bin>startup.bat

The first time you do this it will probably hang and you would need to update the startup.bat and give the JVM some more memory to work with:

set "JAVA_OPTS=-Xms128m -Xmx768m -XX:PermSize=256m -XX:MaxPermSize=512m"
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
...

When you got Alfresco Tomcat started from command line access the server side JavaScript debugger via the http://localhost:8080/alfresco/service/api/javascript/debugger URL (note. You need to be on the machine that runs the JVM you are debugging). Click the Enable button to make the Java Swing debugging application appear.

The JavaScript debugger should look something like this:

Add new comment