Cache control in Alfresco JS Webscripts

Usually the more users your Alfresco instance has, the more time some of your WebScript need to respond (in simplification). Sometimes you can just use the cache mechanism available in the browser to improve performance. Alfresco Webscripts gives you tools to easily define how does browser may cache a server response.

Cache controls

In a webscript descriptor you could use these options:

<cache>
  <never>false</never> false if you want to use a cache
  <public>false</public> true if response should be cached by public caches. Responses connected to given user e.g. list of his sites should not be stored in public cache  (in that case you should choose the false option)
  <mustrevalidate>true</mustrevalidate> I think in 99% causes you should use true, because false means that given WebScript response will never change
</cache>

On the JavaScript’s server side, Alfresco provides the cache root object. The 3 above parameters can also be set via cache object as follows:

cache.neverCache = false;
cache.isPublic = false;
cache.mustRevalidate = true;

Furthermore we have 3 other controls:

cache.maxAge = 86400; // you can set a maximum amount of time that the response will be considered fresh (in seconds)

or/and you can use:

cache.lastModified = (any date object);
cache.ETag = (any string);

In that case you can set a date or a string to check if the answer has changed (the ETag is an identifier for a specific version of a resource), because each next request will have If-Modified-Since and/or If-None-Match in headers object:

headers["If-Modified-Since"] // lastModified date
headers["If-None-Match"] // ETag string

Example

Let’s assume that you are using an issue log based on a datalist module in your site and you want to create a webscript that return a few properties of some issues for a given data list.

File: getJson.get.js

The Web Script controller firstly will check parameters and next return the list of issues or 304 code if nothing has not changed since last user’s request.

var main = function() {
	
	if(args.nodeRef == null) {
		status.code = 400;
    	status.redirect = true;
		return;
	}

	var issueList = search.findNode(args.nodeRef);
	if(issueList == null) {
		status.code = 400;
    	status.redirect = true;
		return;
	}
	
	// (1)
	if(headers["If-Modified-Since"] != null && Math.floor(Date.parse(headers["If-Modified-Since"])/1000) >= Math.floor(issueList.properties["cm:modified"].getTime()/1000)) {
		status.code = 304; // 304 = Not Modified (2)
		status.redirect = true;
		return;
	}

	var responseOb = {};
	var issues = issueList.getChildAssocsByType("dl:issue");
	responseOb.list = [];
	responseOb.totalIssues = issues.length;
	
	for(var i in issues) {
		var issue = issues[i];
		var ob = {};
		
		ob.id = issue.properties["dl:issueID"];
		ob.description = issue.properties["cm:description"];
		ob.priority = issue.properties["dl:issuePriority"];
		ob.status = issue.properties["dl:issueStatus"];
		
		responseOb.list.push(ob);
	}
	cache.lastModified = issueList.properties["cm:modified"];
	model.result = jsonUtils.toJSONString(responseOb);
}

main();

Comments:
(1) – If-Modified-Since header has only accuracy of seconds, that is why I divide the time by 1000 (ms)
(2) – If nothing has changed return 304 code

File: getJson.get.desc.xml

In the descriptor file you should set cache options as I mentioned earlier.

<webscript>
	<shortname>Get PdfJs JSON</shortname>
	<description></description>
	<url>/p0n3/issues</url>
	<format default="json">any</format>
	<authentication>user</authentication>
	<family>p0n3</family>
	<cache>
		<never>false</never>
		<public>false</public>
		<mustrevalidate>true</mustrevalidate>
	</cache>
</webscript>

File: getJson.get.json.ftl

The template file will contain only the value of variable that I set in the script controller.

${result}

Response

After deploying the Web Script files, invoke it using the URL with a nodeRef in a browser e.g:
http://localhost:8080/alfresco/service/p0n3/issues?nodeRef=workspace://SpacesStore/a534356f-8dd6-4d9a-8ffb-dc1adb140c01

{
   "list":[
      {
         "id":"Issue 1",
         "description":"Support need to be able to access and update content of the corporate web site. Need to find a solution.",
         "priority":"Normal",
         "status":"Not Started"
      },
      {
         "id":"Issue 3",
         "description":"The budget has been cut. Need to address the cuts and work out how accomodate the project.",
         "priority":"High",
         "status":"Not Started"
      },
      {
         "id":"Issue 2",
         "description":"There is an issue with the copyright of one of the images selected. Need to source a replacement.",
         "priority":"High",
         "status":"In Progress"
      },
      {
         "id":"Issue 4",
         "description":"The Web Manager has resigned. Need to find a replacement.",
         "priority":"High",
         "status":"Complete"
      }
   ],
   "totalIssues":4.0
}

If you refresh this page again the WebScript return 304 response, so a browser will load JSON content from browser’s cache.

Developer tools - sample requests
Developer tools – sample requests

When you add a new issue, the modified time will change. The WebScirpt returns updated response BUT when you change one of the issues the time won’t change. Why? Because by default cm:modified attribute is updated when nodeRef’s child was added or removed, but not when it was changed. You can easly fix that e.g. by using a script rule on DataList node as follows:

File: updateParentTime.js

var parent = document.getParent();

var ctx = Packages.org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext();
var policyBehaviourFilter = ctx.getBean('policyBehaviourFilter', Packages.org.alfresco.repo.policy.BehaviourFilter);

try {
	policyBehaviourFilter.disableBehaviour(parent.getNodeRef(), Packages.org.alfresco.model.ContentModel.ASPECT_AUDITABLE); // (1)
	parent.properties["cm:modified"] = new Date();
	parent.save();
}
finally {
	policyBehaviourFilter.enableBehaviour(parent.getNodeRef(), Packages.org.alfresco.model.ContentModel.ASPECT_AUDITABLE);  // (1)
}

(1) It is important to turn off a standard policy behaviour, otherwise changing cm:modified will not work.

Sources

Be the first to comment

Leave a Reply

Your email address will not be published.