Introducing the User Script Console

The Alfresco JavaScript Console is really popular among Alfresco administrators and developers to easy automate tasks around Alfresco. This leads to more and more requests from users to have their problems fixed and tasks automated and keeps developers and administrators from more important tasks.

With the new User Script Console extension for Alfresco Share the users finally can access the powerful JavaScript API directly by themselves without needing a specialist. It integrates as a page in a Share site:

Of cause it can not be expected of end users to know JavaScript or the Alfresco APIs right away. That is why the User Script Console slowly introduces the users to the capabilities of the API using a Gamification approach. Users start out as novices and can gain experience (XP) while using the JavaScript API.

User “Experience”

API access gradually changes with increased experience:

  • 0 XP = No Alfresco API access
  • 10 XP = Read properties and permissions, only document root object
  • 1000 XP = Add comments, add Tags to any node
  • 5000 XP = Write properties and add tags, Site API, full ScriptNode api
  • 10000 XP = Groups API (Add/Remove people from sites)
  • 20000 XP = Full Alfresco Javascript API
  • 50000 XP = runas admin, access beans using Packages.java

A new user starting out as a novice must first write a syntactically correct JavaScript using given examples. He receives 1 XP for each successful transaction and will soon gain access to some parts of the Alfreco API (XP of 10 or more). Commands above his experience level are not available:

Examples

To help users find their way around, there are examples in the “Examples” menu that are adjusted to the users experience level. It starts out with a list of simple JavaScript examples for “for”-loops and “if”-clauses.

Badges

Additionally users can earn badges for special achievements (each earns 500 XP):

  • Unlocker: Unlocked more than 10 documents that were locked by the Sharepoint API.
  • Tagmaster: Added 100 different tags to documents
  • Ninja: more than 10 “hidden” property changes with behaviourfilter.disableBehaviour()
  • Shapeshifter: More than 100 calls of the transformDocument/transformImage
  • JsGuru: Run 10 consecutive scripts that all JsLint without any warnings
  • Loadtester: Your last 10 scripts have all run longer than 30s
  • MrClean: Purge the archive store (nodeArchiveService)
  • Reporter: Generation of more than 10000 lines of print output
  • Bouncer: Removed at least 100 people from groups/sites
  • Hacker: Usage of Packages.java to access Spring beans directly

The new User Script Console will help to empower the savvy user to automate Alfresco in an unprecedented way and lets administrators focus on more important tasks like backup and restore of the Alfresco repository.

Available today for Alfresco 4.1.

Recursive file iteration in Java

In a java frameworks talk I gave recently I showed the following example for finding files recursively…

public void listFilesInDirectory(File dir) {
  File[] files = dir.listFiles();
    if (files != null) {
      for (File f : files) {
         if (f.isDirectory()) {
	    listFilesInDirectory(f);
	 }
	 else {
	    System.out.println(f.getName());
	}
     }
  }
}

… which in real projects often grows to a larger block of code. The web is full of code blocks like this for walking a directory tree. The best version I came across is this object oriented one by Torsten Curdt. Since you usually don’t want to write this yourself, I suggested in my talk to use FileUtils which makes recursive iteration much easier:

Collection jspFiles = FileUtils.listFiles(rootDirName,
                        new String[] { "jsp" }, true);

This looks concise and useful but as I tried to use it, I wasn’t too pleased with the FileUtils’ solution. Here is why:

  • The recursion is processed in one go, i.e. all results are written to a List even when using the iterateFiles method. The recursion is not processed iteratively.
  • You can not influence the directories that are searched.
  • Only files are returned, you can not search for directories.
  • The API is not very expressive (e.g. what does the “true” mean).
  • No generics (raw collection types are returned).

A Better API

Not being satisfied with the solutions I found, I “dreamed up” my own API for listing and finding files. I don’t consider it complete but for the most part I am pleased with the ease of use that the builder pattern provides. The code for this can currently be found in an unrelated goole code project. The rest of this article shows the functions that are currently supported.

Find files two ways

There are generally two ways to use the result – as interator or as list:

1. Iterate over all files in the windows directory:

for (File f : Files.find("c:\\windows")) {

}

2. Get all the files in a directory as a list of files:

List<File> allFiles = Files.find(somedir).list();

Except from the return type the second version does the same as the JDK command listFiles:

File[]  allFiles = (new File(somedir)).listFiles()

Easy recursive listing

To iterate all the files in the C:\Windows directory, you would use:

for (File f : Files.find("c:\\windows").recursive()) {

}

Note: This actually works iteratively, i.e. the recursion happens as you fetch files from the iterator. The result is not fetched into a huge list.

With a Predicate you can limit the recursion to specific directories. In this example all .svn directories within a source tree are skipped:

Predicate<File> noSvnDirs = new Predicate<File>() {
boolean apply(File file) {
return !file.getName().equals(".svn");
}
}
for (File f : Files.find("src/java/").recursive(noSvnDir)) {

}

Want Files, Directories or both?

Define if you want only files, only directories or both in your result with yield*()-Methods.

Files.find(someBaseDir).recursive().yieldFiles()  // this is the default
Files.find(someBaseDir).recursive().yieldDirectories()
Files.find(someBaseDir).recursive().yieldFilesAndDirectories()

Filtering the results

To get all textfiles within a dir use:

Files.find(dir).withExtension("txt").list();
Files.find(dir).ignoreCase().withExtension("txt").list();

You can also filter by Name, e.g. to find README files:

Files.find(dir).withName("README").list();
Files.find(dir).ignoreCase().withName("readme").list();

Note that the default matching is case sensitive. The commands caseSensitive() and ignoreCase() can be used to toggle the matching behaviour.

For special needs you can also specify a Predicate<File> to filter the resulting files.

Files.find(dir).recursive().withFilter(somePredicate).list();

Finding Directories

When looking for directories there are some special usecases that are supported, e.g. looking for directories that contain a specific file:

Files.find(dir).recursive().yieldDirectories()
               .containingFile("Thumbs.db");