Monday, December 1, 2008

Learning Surf 6 - Creating a webscript service

In the previous blog, we created a simple dialog to open a content creation form from Share's Document Library page toolbar when we click on our added 'New Content' button. This form invokes the POST for 'slingshot/doclib/action/folder/site/{site}/{container}/{path}' service implemented as part of the RESTful Remote API of Afresco 3.0. As we left it, the 'New Content' button simply creates a folder doing the same job as the 'New Folder' button.

Step 1 - create a new service

Now we need to create a webscript service to use instead of the slingshot/doclib/action/folder service. for this blog, I will extend the action services to implement the following url pattern:

POST slingshot/doclib/action/file/site/{site}/{container}/{path}

To do this, we will add a new webscript service to the remote api project. Later, we can separate this to our own amp module. As a starting point, we can copy the folder.post.* files to create file.post.* files in Remote API/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action directory. This creates the following files:
  • file.post.desc.xml
  • file.post.json.ftl
  • file.post.json.js
Now we can modify these files to do the work of creating a file instead of a folder. for 'file.post.desc.xml' we modify the url pattern to the following:
<webscript>
<shortname>folder</shortname>
<description>Document List Action - Create folder</description>
<url>/slingshot/doclib/action/file/site/{site}/{container}</url>
<url>/slingshot/doclib/action/file/site/{site}/{container}/{path}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
</webscript>

file.post.json.ftl file does not need to be changed, it imports the action.lib.ftl to create a standard results response json format.
file.post.json.js needs to be modified to handle a file instead of a folder.

The main changes I will make to this script:
  1. get a 'content' field from the json request object from the create-content form that we added previously.
     if (!json.isNull("content"))
    {
    content = json.get("content");
    }
  2. Rename variables folderName, folderDescription, folderTitle, folderPath to fileName, fileDescription, fileTitle, fileDescription and filePath respectively.
  3. Create a file node instead of a folder node
    var fileNode = parentNode.createFile(fileName);

  4. set the content on the newly created fileNode variable arfter the fileNode.save(); method
          fileNode.save();
    // Add uifacets aspect for the web client
    fileNode.content = content;
  5. Change messaging to reflect saving a file, not a folder.
The completed file.post.json.js looks like this:
<import resource="classpath:/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/action.lib.js">

/**
* Create file action
* @method POST
* @param uri {string} /{siteId}/{containerId}/{filepath}
* @param json.name {string} New file name
* @param json.title {string} Title metadata
* @param json.description {string} Description metadata
* @param json.content {string} Content of file
*/

/**
* Entrypoint required by action.lib.js
*
* @method runAction
* @param p_params {object} common parameters
* @return {object|null} object representation of action result
*/
function runAction(p_params)
{
var results;

try
{
// Mandatory: json.name
if (json.isNull("name"))
{
status.setCode(status.STATUS_BAD_REQUEST, "File name is a mandatory parameter.");
return;
}
var fileName = json.get("name");

var parentPath = p_params.path;
var filePath = parentPath + "/" + fileName;

// Check file doesn't already exist
var existsNode = getAssetNode(p_params.rootNode, filePath);
if (typeof existsNode == "object")
{
status.setCode(status.STATUS_BAD_REQUEST, "File '" + filePath + "' already exists.");
return;
}

// Check parent exists
var parentNode = getAssetNode(p_params.rootNode, parentPath);
if (typeof parentNode == "string")
{
status.setCode(status.STATUS_NOT_FOUND, "Parent folder '" + parentPath + "' not found.");
return;
}

// Title and description
var fileTitle = "";
var fileDescription = "";
if (!json.isNull("title"))
{
fileTitle = json.get("title");
}
if (!json.isNull("description"))
{
fileDescription = json.get("description");
}
if (!json.isNull("content"))
{
content = json.get("content");
}
// Create the folder and apply metadata
var fileNode = parentNode.createFile(fileName);
// Always add title & description, default icon
fileNode.properties["cm:title"] = fileTitle;
fileNode.properties["cm:description"] = fileDescription.substr(0, 100);
fileNode.properties["app:icon"] = "space-icon-default";
fileNode.save();
// Add uifacets aspect for the web client
fileNode.content = content;
fileNode.addAspect("app:uifacets");

// Construct the result object
results = [
{
id: filePath,
name: fileName,
parentPath: parentPath,
nodeRef: fileNode.nodeRef.toString(),
action: "createFile",
success: true
}];
}
catch(e)
{
status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, e.toString());
return;
}

return results;
}

/* Bootstrap action script */
main();

This service uses the action.lib.js and action.lib.ftl libraries to simplify coding of similar functions, including: checkin, checkout, cancel-checkout, copy-to, move-to, file or folder delete and others. The action.lib.js is included in the folder.post.json.js script and provides a main() entry point to do common setup, clean up and host shared functions. The main entry point calls the runAction() function defined in the specific action script 'folder.post.json.js', effectively implementing a strategy pattern.

Step 3 - build and test
Since we have changed the Remote API source code, we must build alfresco.war and deploy to tomcat. I use the default ant task:
ant build-tomcat

to make sure all my changes are built. This will deploy share.war and alfresco.war.
Start up tomcat.
To view the new service, use the url http://localhost:8081/alfresco/service/index/uri/slingshot/doclib/action/file/site/%7Bsite%7D/%7Bcontainer%7D which should show the service correctly added.

Step 4 - alter 'New Content' button action to invoke new service
Now we have to use this new service we have created when we press the 'new content' button we added in the previous post. Change the toolbar.js onNewContent action line to read:
         var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "slingshot/doclib/action/file/site/{site}/{container}/{path}",

this will call our new slingshot service to create a file, which in turn will call the new remote api service we added to alfresco.

Step 5 - deploy and test
Now, you should be able to press 'New Content' in share Document Library and create a new content item.

2 comments:

Anonymous said...

Hi Ed,

really good series. Just a couple of things on this last one. In the xml for file.post.desc.xml you have not changed folder to file.
Also don't seem to have the bit for changing the action on the button, needs to change toolbar.js

var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "slingshot/doclib/action/folder/site/{site}/{container}/{path}",

to

var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "slingshot/doclib/action/file/site/{site}/{container}/{path}",


Cheers,

Chris

Sebastian said...

Hi Ed, your blog is very helpfull.
I need to create my own content model instead the default content model.
How can I set the type?
thanks in advance.
Sebastian