Placing new tooling on existing worksites en masse is a classic Sakai administration problem. Creating new worksites as copies of other sites is easy enough, but subsequent changes to the original site are not reflected in the copy. So if an institution decides, for example to push Melete to all course worksites midway through a semester, its options are somewhat limited since neither Sakai's APIs or its UI expose first-class bulk tool placement features. Thus institutions generally fall back to database or webservice scripting to cascade new tool placements to subsets of existing sites. As Ian Boston pointed out in his reply to the thread linked above, SASH, i.e. the SAkai SHell, is one such ad hoc scripting option.
SASH is a Sakai tool implemented in the RSF stack which presents a terminal-like UI:

Deployment against a 2.5+ Sakai instance is quite straightforward. Checkout the source from the Contrib repo and kick off a Maven build:
$> cd sakai-trunk-wc
$> svn co -q https://source.sakaiproject.org/contrib/sakaiscript/sash/trunk/ sash
$> cd sash
$> mvn clean install sakai:deploy
Following deployment and a Sakai restart, place the "HTML Terminal" tool on the super-user's worksite. You'll find that you'll need to be a super user to interact with SASH and that you must not be using the default password.
SASH ships with several bash-like builtins at varying levels of completeness. For example, here we see a simple execution of "echo":

More interestingly, though, SASH also ships with a collection of "add-ons" which include a set of administrative utility commands and script interpreter launchers. For example, here we see an administrator using the "site" command to create a new worksite and place a new page and tool on it:
1. Browse command usage

2. Create a new site

3. Create a new page and place the schedule tool on it

4. Navigate to the site and confirm the presence of the schedule tool.

One option, then, for scripting tool placements across multiple sites would be to find the Java class implementing the "site" command (SiteUtility) and extend it to support additional arguments. Or, we could implement our own command altogether, possibly leveraging the SiteUtility API where appropriate. It turns out that registering new commands is quite straight forward.
At initialization time, a SpringBeanCommandFinder collaborator injected into the SashInterpreter seeks out beans registered with its ApplicationContext which implement the BaseBuiltin or BaseCommand types.
So, to enable a custom SASH command, we can code an extension of BaseCommand and bundle it into a Sakai component. For example, a Hello World command complicated for demonstration purposes might be implemented as follows:
package org.sakaiproject.sash.extension.demo;
import org.sakaiproject.sash.api.BaseCommand;
public class HelloWorldCommand extends BaseCommand {
public String getUsage() {
return getName() +
" - Prints an optionally modified Hello World message \n" +
"Usage: \n" +
"\t " + getName() + " MODIFIER \n\n";
}
public String getName() {
return "hw";
}
public void execute(String[] argv) {
String modifier = argv.length >= 1 ? argv[0] : null;
modifier = modifier == null ? " " : " " + modifier + " ";
if ( " ? ".equals(modifier) ) {
println(getUsage());
} else {
println("Hello" + modifier + "World");
}
}
}
The component definition consists of a single bean:
Following Maven-executed build and deployment of this command and a Sakai restart, we can invoke it from the SASH command line using the name defined by HelloWorldCommand.getName():

While the command coding and registration process is relatively painless, it's not an appealing workflow from a system administrator's point of view. Setting up the source code project is not exactly brainless and deploying new components to a production Sakai system is potentially a non-starter since SASH will not detect new or modified command implementations without a restart. More convenient would be the ability to actually script SASH to perfrom administrative tasks without involving the standard compile -> deploy -> restart process. Enter SASH's "scripting support" add-ons. These commands launch dynamic language interpreters, passing script streams read from Sakai's content hosting service. Out-of-the-box support exists for bootstrapping BeanShell, Jython and Groovy scripts.
To demonstrate a SASH-launched Groovy script we'll skip yet another pass at "Hello World" and look at solving our original problem of pushing new tool placements to existing worksites.
Here's an attempt at solving the problem in Groovy by retrieving the appropriate components from the ComponentManager cover and exercising their APIs. Note that Groovy has no trouble interacting directly with Sakai's Java interfaces. No special bridging configuration is necessary. The syntax should be relatively accessible to Java developers:
// addtool.groovy
// Import directly-referenced interfaces
import org.sakaiproject.component.cover.ComponentManager
import org.sakaiproject.site.api.SiteService
// SASH will send the script name as the first arg, hence the arg
// list size must be one greater than the required arg count
assert args.size() >= 3 , "Must specify a tool ID and a page name"
// Retrieve references to Sakai components
siteService = ComponentManager.get("org.sakaiproject.site.api.SiteService")
toolManager = ComponentManager.get("org.sakaiproject.tool.api.ToolManager")
// A block that tests for the given tool's presence on the given site
def toolAlreadyPlaced = { site, toolId ->
site.getToolForCommonId(toolId) != null
}
// A block that creates a new page on the given site and places
// the referenced tool on that page. The page and tool will have
// the same name
def placeTool = { site, toolId, pageName ->
siteEdit = siteService.getSite(site.id)
sitePageEdit = siteEdit.addPage()
sitePageEdit.setTitle(args[2])
sitePageEdit.setLayout(0)
toolConfig = sitePageEdit.addTool()
toolConfig.setTool(toolId, toolManager.getTool(toolId))
toolConfig.setTitle(pageName)
siteService.save(siteEdit)
}
// Now the script's "main" section. Caches CLI args and passes
// a block to SiteService.getSites() for site-by-site processing
def toolId = args[1]
def pageName = args[2]
println "Adding tool to sites [tool ID: ${toolId}] [page/tool title: ${pageName}]"
siteService.getSites(SiteService.SelectionType.NON_USER, ["course","project"], null,
null, null, null).each { site ->
println "Processing site [title: ${site.title}] [id: ${site.id}]"
if ( site.id == "citationsAdmin" ) {
println "Skipping Citations Admin site..."
} else if ( toolAlreadyPlaced(site, toolId) ) {
println "Skipping site: tool is already placed on this site..."
} else {
println "Adding tool to site..."
placeTool(site, toolId, pageName)
}
}
Following upload via the Resources tool, SASH can execute the script by passing its path to the "groovy" command:
1. Upload the Groovy source to the /user/admin directory

2. Execute the script from SASH

3. Navigate to an affected site and verify new tool placement

Suppose, though, we were concerned that site lookups and saves don't occur with a single database transaction and in fact we'd like the entire script to execute in a single transaction. We can take advantage of the fact that the default SiteService implementation delegates to SqlService, modify our Groovy script, re-upload it, and re-execute it from SASH without bouncing Sakai. For reference, here's what such a modified script might look like:
// addtool.groovy (transactional)
// Import directly-referenced interfaces
import org.sakaiproject.component.cover.ComponentManager
import org.sakaiproject.site.api.SiteService
// SASH will send the script name as the first arg, hence the arg
// list size must be one greater than the required arg count
assert args.size() >= 3 , "Must specify a tool ID and a page name"
// Retrieve references to Sakai components
siteService = ComponentManager.get("org.sakaiproject.site.api.SiteService")
toolManager = ComponentManager.get("org.sakaiproject.tool.api.ToolManager")
sqlService = ComponentManager.get("org.sakaiproject.db.api.SqlService")
// A block that tests for the given tool's presence on the given site
def toolAlreadyPlaced = { site, toolId ->
site.getToolForCommonId(toolId) != null
}
// A block that creates a new page on the given site and places
// the referenced tool on that page. The page and tool will have
// the same name
def placeTool = { site, toolId, pageName ->
siteEdit = siteService.getSite(site.id)
sitePageEdit = siteEdit.addPage()
sitePageEdit.setTitle(args[2])
sitePageEdit.setLayout(0)
toolConfig = sitePageEdit.addTool()
toolConfig.setTool(toolId, toolManager.getTool(toolId))
toolConfig.setTitle(pageName)
siteService.save(siteEdit)
}
// A block that implements the site-by-site processing loop
def placeToolOnSites = { toolId, pageName ->
println "Adding tool to sites [tool ID: ${toolId}] [page/tool title: ${pageName}]"
siteService.getSites(SiteService.SelectionType.NON_USER, ["course","project"], null,
null, null, null).each { site ->
println "Processing site [title: ${site.title}] [id: ${site.id}]"
if ( site.id == "citationsAdmin" ) {
println "Skipping Citations Admin site..."
} else if ( toolAlreadyPlaced(site, toolId) ) {
println "Skipping site: tool is already placed on this site..."
} else {
println "Adding tool to site..."
placeTool(site, toolId, pageName)
}
}
}
// A block that wraps placeToolOnSites in a Runnable implementation
// such that it can be passed to SqlService.transact() and execute
// as a single db transaction.
def txn = {
def toolId = args[1]
def pageName = args[2]
placeToolOnSites(toolId, pageName)
} as Runnable
// Now the script's "main" section.
sqlService.transact(txn, "Adding Tools")
Having the ability to script Sakai with a JVM-hosted dynamic language is, I must admit, just downright cool. And to the extent that being able to upload and execute arbitrary scripts in a Sakai environment empowers system administrators and prototypers to quickly get work done, SASH is certainly a Good Thing.
Of course, SASH scripting it isn't entirely without risk. The SASH engine requires that it run as a Sakai super-user, giving its commands and scripts plenary access to any Sakai API. Additionally, since SASH doesn't run in a specialized ClassLoader or otherwise protected space, it can effectively "do" anything your JVM can do, including access the server side file system. Script development in general also tends to be somewhat naive, especially in terms of error handling and recovery (just look at my Groovy code!), and is rarely subjected to the same kind of automated testing one expects from "mainline" development. It's easy to forget that scripts which are so easy to put together are themselves pieces of "production code" and as such may require somewhat more planning than simply "hack, upload and run." For example, in our tool placement script, we cannot predict the size of the transaction resulting from any given execution. What impact would an exceptionally large tool placement transaction have on a production-deployed Sakai instance? Are we drastically increasing the likelihood of live- or deadlocking on database resources? Perhaps the script should throttle transactions into predictable "batches" to better amortize the cost of the larger process.
As they say... with the power comes the responsibility...

Aderes The yes ¿?
Thank you very much for this information. I like this site
----------
sohbet ~ sohbet odaları ~ muhabbet
It's fantastic information.
It's fantastic information. Thank you!
----------
mIRC indir
Thank you
Hi friends,
I have read the article and i like it too much. Thanks a lot for this.
----------------
sohbet
Build problems
Ok - so I decided to start this thing from anew.
I checked it out:
svn co https://source.sakaiproject.org/contrib/sakaiscript/sash/trunk sash
I modified the pom./xml at the top of the 2.5 tree
(there are two places in that file where you and made module entries - I'm wondering
if I did the right entry) adding a module entry for sash.
then I cd'd into sash and did a:
mvn clean install sakai:deploy
what came back is an indication that it can't load something from any repository.
However, I believe I have followed the instructions in the README to the letter
and made the mod to the pom.xml suggested. Is there something else I
need to do ?
- George Pipkin
bash-3.2# mvn clean install sakai:deploy
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[ERROR] FATAL ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to resolve artifact.
GroupId: org.sakaiproject
ArtifactId: master
Version: SNAPSHOT
Reason: Unable to download the artifact from any repository
org.sakaiproject:master:pom:SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
[INFO] ------------------------------------------------------------------------
[INFO] Trace
org.apache.maven.reactor.MavenExecutionException: Cannot find parent: org.sakaiproject:master for project: org.sakaiproject:sakai-sash-base:pom:null for project org.sakaiproject:sakai-sash-base:pom:null
at org.apache.maven.DefaultMaven.getProjects(DefaultMaven.java:376)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:289)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:126)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:282)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
Caused by: org.apache.maven.project.ProjectBuildingException: Cannot find parent: org.sakaiproject:master for project: org.sakaiproject:sakai-sash-base:pom:null for project org.sakaiproject:sakai-sash-base:pom:null
at org.apache.maven.project.DefaultMavenProjectBuilder.assembleLineage(DefaultMavenProjectBuilder.java:1259)
at org.apache.maven.project.DefaultMavenProjectBuilder.buildInternal(DefaultMavenProjectBuilder.java:745)
at org.apache.maven.project.DefaultMavenProjectBuilder.buildFromSourceFileInternal(DefaultMavenProjectBuilder.java:476)
at org.apache.maven.project.DefaultMavenProjectBuilder.build(DefaultMavenProjectBuilder.java:197)
at org.apache.maven.DefaultMaven.getProject(DefaultMaven.java:548)
at org.apache.maven.DefaultMaven.collectProjects(DefaultMaven.java:458)
at org.apache.maven.DefaultMaven.getProjects(DefaultMaven.java:362)
... 11 more
Caused by: org.apache.maven.project.ProjectBuildingException: POM 'org.sakaiproject:master' not found in repository: Unable to download the artifact from any repository
org.sakaiproject:master:pom:SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
for project org.sakaiproject:master
at org.apache.maven.project.DefaultMavenProjectBuilder.findModelFromRepository(DefaultMavenProjectBuilder.java:571)
at org.apache.maven.project.DefaultMavenProjectBuilder.assembleLineage(DefaultMavenProjectBuilder.java:1255)
... 17 more
Caused by: org.apache.maven.artifact.resolver.ArtifactNotFoundException: Unable to download the artifact from any repository
org.sakaiproject:master:pom:SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
at org.apache.maven.artifact.resolver.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:206)
at org.apache.maven.artifact.resolver.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:73)
at org.apache.maven.project.DefaultMavenProjectBuilder.findModelFromRepository(DefaultMavenProjectBuilder.java:524)
... 18 more
Caused by: org.apache.maven.wagon.ResourceDoesNotExistException: Unable to download the artifact from any repository
at org.apache.maven.artifact.manager.DefaultWagonManager.getArtifact(DefaultWagonManager.java:324)
at org.apache.maven.artifact.resolver.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:194)
... 20 more
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Mon Apr 21 11:58:54 EDT 2008
[INFO] Final Memory: 1M/452M
[INFO] ------------------------------------------------------------------------
bash-3.2#
bash-3.2#
Credit Where Credit is Due
I was remiss in failing to cite Steve Githens as the primary (only?) SASH committer. Nice work, Steve.
Unable to get this to build
Hi -
I got a copy iof SASH from contrib, and I tried to mvn it, and
this is what happened :
bash-3.2# mvn clean install sakai:deploy
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[ERROR] FATAL ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to resolve artifact.
GroupId: org.sakaiproject
ArtifactId: master
Version: SNAPSHOT
Reason: Unable to download the artifact from any repository
org.sakaiproject:master:pom:SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
[INFO] ------------------------------------------------------------------------
[INFO] Trace
org.apache.maven.reactor.MavenExecutionException: Cannot find parent: org.sakaiproject:master for project: org.sakaiproject:sakai-sash-base:pom:null for project org.sakaiproject:sakai-sash-base:pom:null
at org.apache.maven.DefaultMaven.getProjects(DefaultMaven.java:376)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:289)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:126)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:282)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
Caused by: org.apache.maven.project.ProjectBuildingException: Cannot find parent: org.sakaiproject:master for project: org.sakaiproject:sakai-sash-base:pom:null for project org.sakaiproject:sakai-sash-base:pom:null
at org.apache.maven.project.DefaultMavenProjectBuilder.assembleLineage(DefaultMavenProjectBuilder.java:1259)
at org.apache.maven.project.DefaultMavenProjectBuilder.buildInternal(DefaultMavenProjectBuilder.java:745)
at org.apache.maven.project.DefaultMavenProjectBuilder.buildFromSourceFileInternal(DefaultMavenProjectBuilder.java:476)
at org.apache.maven.project.DefaultMavenProjectBuilder.build(DefaultMavenProjectBuilder.java:197)
at org.apache.maven.DefaultMaven.getProject(DefaultMaven.java:548)
at org.apache.maven.DefaultMaven.collectProjects(DefaultMaven.java:458)
at org.apache.maven.DefaultMaven.getProjects(DefaultMaven.java:362)
... 11 more
Caused by: org.apache.maven.project.ProjectBuildingException: POM 'org.sakaiproject:master' not found in repository: Unable to download the artifact from any repository
org.sakaiproject:master:pom:SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
for project org.sakaiproject:master
at org.apache.maven.project.DefaultMavenProjectBuilder.findModelFromRepository(DefaultMavenProjectBuilder.java:571)
at org.apache.maven.project.DefaultMavenProjectBuilder.assembleLineage(DefaultMavenProjectBuilder.java:1255)
... 17 more
Caused by: org.apache.maven.artifact.resolver.ArtifactNotFoundException: Unable to download the artifact from any repository
org.sakaiproject:master:pom:SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
at org.apache.maven.artifact.resolver.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:206)
at org.apache.maven.artifact.resolver.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:73)
at org.apache.maven.project.DefaultMavenProjectBuilder.findModelFromRepository(DefaultMavenProjectBuilder.java:524)
... 18 more
Caused by: org.apache.maven.wagon.ResourceDoesNotExistException: Unable to download the artifact from any repository
at org.apache.maven.artifact.manager.DefaultWagonManager.getArtifact(DefaultWagonManager.java:324)
at org.apache.maven.artifact.resolver.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:194)
... 20 more
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Mon Apr 07 12:00:08 EDT 2008
[INFO] Final Memory: 1M/452M
[INFO] ------------------------------------------------------------------------
Any ideas on how to get it to build ?
- George Pipkin
U.Va
Missing SNAPSHOT Artifact
George,
The SASH trunk POM extends the Sakai "master" POM at the SNAPSHOT version. As far as I know, Sakai SNAPSHOT artifacts aren't available in a public Maven repo, so you _might_ be able to get away with just checking out and building https://source.sakaiproject.org/svn/master/trunk/ before building SASH. I wouldn't be surprised, though, if you needed to build a large subset of the mainline trunk to get a successful SASH build (look at all the Sakai dependencies in ./sash-core-impl/pom.xml). If you're just looking to kick the tires, I would probably just export the Sakai trunk, export the sash module into that, update the top-level modules list, and build the whole thing. If you'd rather not build against trunk, there's READMEs in the SASH tags for building against earlier releases, e.g. https://source.sakaiproject.org/contrib/sakaiscript/sash/tags/sash-PR1/R...
Build problems
Hi -
Thanks for your reply. I still haven't been able to build this thing. I put it in both
my 2.4 and my 2.5 tree and tried both places without a successful result.
I followed the instructions in the README (I think these are for 2.4 - right ?)
svn co https://source.sakaiproject.org/contrib/sakaiscript/sash/tags/sash-PR1
cd sash-PR1
maven bld dpl
That doesn't do much because here's no project.xml at the root of the project, but
there is one down in sash-core so I tried it there and got no love:
bash-3.2# maven bld dpl
__ __
| \/ |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \ ~ intelligent projects ~
|_| |_\__,_|\_/\___|_||_| v. 1.0.2
org.apache.maven.MavenException: Error reading XML or initializing
at org.apache.maven.MavenUtils.getProject(MavenUtils.java:156)
at org.apache.maven.MavenUtils.getProject(MavenUtils.java:122)
at org.apache.maven.MavenSession.initializeRootProject(MavenSession.java:23
I also tried it in the 2.5 tree. When you build the whole sakai 2.5 it happily goes
through it, but nothing like sakai.htmlterm appears as a tool. In fact, nothing
at all appears deployed or built. I suspect that's because there's not pom's
anywhere about to be found. I'd really like to check this thing out - what
do I need to do to accomplish that ?
- George Pipkin
U.Va.
Hmmm
George,
When you built your entire 2.5 tree + SASH did you add "sash" to the list of modules in the source tree's root pom, i.e. your local copy of https://source.sakaiproject.org/svn/sakai/tags/sakai_2-5-0/pom.xml?
If not, I think that would explain the silence at build time.
Once "sash" is in the module list, I would expect the build to fail if you haven't built the post-2.5 trunk locally. To fix this I believe you need to go through the sash module and replace all occurrences of "M2" with "SNAPSHOT".
- Dan
Hi
Thanks for the input. I'm close to making a 0.1.0 tag soon, and will make instructions/setup for building against trunk and 2.5.x. As well as binaries as soon as I fix this last problem that requires manually copying over a bulk of commands manually.
Also, awesome write up Dan! Especially considering how little documention there is! Looking forward to having more people use it and contribute feedback, code, and scripts!
-Steve
very god work thanks you
It's fantastic information. Thank you
_ _ _ _ _ _ _ _ _ _
sohbet Saç Ekimi