<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ecmarchitect.com &#187; Alfresco Developer Series</title>
	<atom:link href="http://ecmarchitect.com/categories/content-management/alfresco-developer-series/feed" rel="self" type="application/rss+xml" />
	<link>http://ecmarchitect.com</link>
	<description>Jeff Potts on ECM, portals, search, collaboration, and a bunch of personal stuff</description>
	<lastBuildDate>Tue, 15 May 2012 07:05:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Alfresco Example: Share Dashlet Part Two&#8211;Configuration</title>
		<link>http://ecmarchitect.com/archives/2012/05/15/1599</link>
		<comments>http://ecmarchitect.com/archives/2012/05/15/1599#comments</comments>
		<pubDate>Tue, 15 May 2012 07:05:43 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Dashlet]]></category>
		<category><![CDATA[Dashlet Configuration]]></category>
		<category><![CDATA[Example]]></category>
		<category><![CDATA[Share Customization]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1599</guid>
		<description><![CDATA[In my last post I showed a simple example that illustrates the &#8220;repository tier data web script/share tier presentation web script&#8221; pattern that is used any time you need to create a dashlet in Alfresco Share that reads data from the Alfresco repository. The example I used is called &#8220;Get Latest Document&#8221; and it does [...]]]></description>
			<content:encoded><![CDATA[<p>In my <a title="ecmarchitect.com Alfresco Example: Share dashlet that talks to the repository" href="http://ecmarchitect.com/archives/2012/05/04/1592">last post</a> I showed a simple example that illustrates the &#8220;repository tier data web script/share tier presentation web script&#8221; pattern that is used any time you need to create a dashlet in Alfresco Share that reads data from the Alfresco repository. The example I used is called &#8220;Get Latest Document&#8221; and it does just that&#8211;given a folder path, it reads some basic metadata about the most recently modified document and displays it in a dashlet.</p>
<p>That example read the folder path from an XML configuration file that is included as part of the Share tier web script. But what if you wanted to make it easier to configure the dashlet by allowing a Share user to open up a configuration dialog that includes a folder picker? Luckily, Alfresco Share already includes all of the bits and pieces you need to make this possible&#8211;it&#8217;s just up to you to wire them together. I&#8217;ll show you how to do that in this post.</p>
<p>The code that implements this example lives in a <a title="Google Code: Get Latest Document Example" href="http://code.google.com/p/alfresco-get-latest-document/">Google Code project</a>. I&#8217;ve tagged the prior post&#8217;s source code&#8211;the &#8220;XML configuration&#8221; code&#8211;as &#8220;<a title="Google Code: Get Latest Document Example 1.0 Tag" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/#svn%2Ftags%2F1.0">1.0</a>&#8220;. The code that goes along with this post is tagged as &#8220;<a title="Google Code: Get Latest Document Example 2.0 Tag" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/#svn%2Ftags%2F2.0">2.0</a>&#8220;. So if you are following along make sure you are using the appropriate code set.</p>
<p>I&#8217;m using Alfresco 4.0.d Community, WAR-only distribution, Lucene for search, two Tomcats, and MySQL. My project is organized following the Share Extras <a title="Google Code: Share Extras Sample Dashlet source" href="http://code.google.com/p/share-extras/source/browse/#svn%2Ftrunk%2FSample%20Dashlet">Sample Dashlet</a> project folder structure.</p>
<p><strong>Here&#8217;s the Recipe</strong></p>
<p>The goal is to take the 1.0 version of the Get Latest Document dashlet and make it configurable through the UI instead of an XML configuration file. Recall from the prior post that we have a repository tier web script that returns JSON for the most recently modified document in a specified folder path. To make that folder path configurable, nothing at all needs to change on the repository tier. All of the changes are going to take place on the Share tier.</p>
<p>Here&#8217;s what has to be created or changed to make this work:</p>
<ol>
<li>Modify the dashlet markup to include a configuration link</li>
<li>Create a custom client-side JavaScript component to handle the configuration dialog and the folder picker</li>
<li>Write a new Share-tier web script that persists the configuration data</li>
</ol>
<p>Not too bad, right? Well let&#8217;s get after it then.</p>
<p><strong>Modify the dashlet markup to handle configuration</strong></p>
<p>In a minute I&#8217;m going to show you the code for the client-side JavaScript component that listens for configuration clicks. For now, I&#8217;ll just point out where that client-side object is instantiated within the dashlet&#8217;s markup. It is near the top of <a title="Google Code: Get Latest Document Example - get-latest-doc.get.html.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.html.ftl">get-latest-doc.get.html.ftl</a>:</p>
<pre>var getLatestDoc = new SomeCo.dashlet.GetLatestDoc("${el}").setOptions(
{
  "componentId": "${instance.object.id}",
  "siteId": "${page.url.templateArgs.site!""}",
  "title": "${title}",
  "filterPath": "${filterPath}",
  "filterPathView": "${filterPathView}"
}).setMessages(${messages});</pre>
<p>Notice the &#8220;SomeCo&#8221; object. When creating new client-side objects, you definitely want to steer clear of the &#8220;Alfresco&#8221; object if you can so that your stuff doesn&#8217;t collide with Alfresco&#8217;s stuff.</p>
<p>Next, I need an &#8220;edit&#8221; icon in the dashlet&#8217;s title bar. I want it to appear only if the user is a site manager. So I&#8217;m going to add a little blurb to the <a title="Google Code: Get Latest Document Example - get-latest-doc.get.html.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.html.ftl">get-latest-doc.get.html.ftl</a> view that does this:</p>
<pre>var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure");
editDashletEvent.subscribe(getLatestDoc.onConfigGetLatestDocClick,
                           getLatestDoc, true);
new Alfresco.widget.DashletTitleBarActions("${args.htmlid?html}").
  setOptions(
{
  actions:
  [
    &lt;#if userIsSiteManager&gt;
    {
      cssClass: "edit",
      eventOnClick: editDashletEvent,
      tooltip: "${msg("dashlet.edit.tooltip")?js_string}"
    },
    &lt;/#if&gt;</pre>
<p>The first part of this code hooks a YUI "onDashletConfigure" event to the "onConfigGetLatestDocClick" handler in the custom GetLatestDoc client-side JavaScript object. The second part is a FreeMarker conditional that is checking the value of "userIsSiteManager". The controller will set that value. I'll show you that shortly. The rest of the view is unchanged from the 1.0 version.</p>
<p>The dashlet needs access to the custom client-side JavaScript component as well as some of Alfresco's out-of-the-box components that I will leverage to open a dialog and to present a folder picker. I've added these to <a title="Google Code: Get Latest Document Example - get-latest-doc.get.head.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.head.ftl">get-latest-doc.get.head.ftl</a>:</p>
<pre>&lt;!-- getLatestDoc --&gt;
&lt;@link rel="stylesheet" type="text/css"
  href="${page.url.context}/res/extension/components/dashlets/get-latest-doc.css" /&gt;
&lt;@script type="text/javascript"
  src="${page.url.context}/res/extension/components/dashlets/get-latest-doc.js"&gt;&lt;/@script&gt;
&lt;!-- Simple Dialog --&gt;
&lt;@script type="text/javascript"
  src="${page.url.context}/modules/simple-dialog.js"&gt;&lt;/@script&gt;
&lt;!-- Global Folder Select --&gt;
&lt;@link rel="stylesheet" type="text/css"
  href="${page.url.context}/modules/documentlibrary/global-folder.css" /&gt;
&lt;@script type="text/javascript"
  src="${page.url.context}/modules/documentlibrary/global-folder.js"&gt;&lt;/@script&gt;</pre>
<p>Now take a look at the dashlet's controller, <a title="Google Code: Get Latest Document Example - get-latest-doc.get.js" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.js">get-latest-doc.get.js</a>. The biggest change here is to add a call to the repository to find out if the current user is a site manager:</p>
<pre>var userIsSiteManager = false,
json = remote.call("/api/sites/" + page.url.templateArgs.site +
  "/memberships/" + encodeURIComponent(user.name));
if (json.status == 200)
{
  var obj = eval('(' + json + ')');
  if (obj)
  {
    userIsSiteManager = (obj.role == "SiteManager");
  }
}
model.userIsSiteManager = userIsSiteManager;</pre>
<p>The rest of the controller is pretty much the same as 1.0, with the minor addition of checking "args" for values that the configuration service is going to pass in. If they don't get passed in, the values will be read from the config XML as they were in 1.0.</p>
<p>That's it for the changes to the dashlet. If you wanted to you could deploy at this point, but clicking the edit button would result in JavaScript errors.</p>
<p><strong>Create a custom client-side JavaScript component</strong></p>
<p>Client-side JavaScript for this example lives in source/web/extension/components in a file called dashlets/<a title="Google Code: Get Latest Document Example - get-latest-doc.js" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/source/web/extension/components/dashlets/get-latest-doc.js">get-latest-doc.js</a>. The "source/web" part of the path is dictated by the Share Extras sample project folder layout. I used "extension" to keep my client-side static assets separate from Alfresco's. You might choose something more unique.</p>
<p>I'm not going to go line-by-line--look at the source for the full detail. The first thing worth noting is at the very beginning of the file: It's a declaration of the "SomeCo" object. The client-side object I define in this file will live in that namespace. If I had other custom client-side objects I'd declare them as part of that namespace as well. I've seen a lot of examples that place their custom client-side objects in the "Alfresco" namespace, which is a bad habit. There's nothing magical about that Alfresco namespace, so why not make it obvious what's part of the product and what's a customization?</p>
<pre>if (typeof SomeCo == "undefined" || !SomeCo)
{
  var SomeCo = {};
  SomeCo.dashlet = {};
}</pre>
<p>Next comes the declaration of the constructor for this new object:</p>
<pre>SomeCo.dashlet.GetLatestDoc = function GetLatestDoc_constructor(htmlId)
{
  SomeCo.dashlet.GetLatestDoc.superclass.constructor.call(this,
    "SomeCo.dashlet.GetLatestDoc", htmlId);
  /**
   * Register this component
   */
  Alfresco.util.ComponentManager.register(this);
  /**
   * Load YUI Components
   */
  Alfresco.util.YUILoaderHelper.require(["button", "container",
    "datasource", "datatable", "paginator", "json", "history",
    "tabview"], this.onComponentsLoaded, this);
  return this;
};</pre>
<p>After calling the superclass&#8217; constructor, I ask the Alfresco ComponentManager to register the class. Then, I use Alfresco&#8217;s YUILoaderHelper to declare the components on which my component depends.</p>
<p>After that, the actual definition of the object begins. My GetLatestDoc object is going to extend Alfresco&#8217;s Base object, so I use YAHOO.extend to make that happen:</p>
<pre>YAHOO.extend(SomeCo.dashlet.GetLatestDoc, Alfresco.component.Base,
{</pre>
<p>What follows are the properties and methods of the GetLatestDoc object. The two big things it takes care of are (1) Responding to the &#8220;Configure&#8221; click and (2) Displaying the folder picker dialog.</p>
<p>The first method is the onConfigGetLatestDocClick. You&#8217;ll remember that from the updates to the view&#8211;I hooked up the &#8220;configure&#8221; link to this method. Here is the declaration followed by specifying the actionUrl. The actionUrl is where the configure form will be posted. In this case it is a web script I&#8217;ll walk you through in the next section.</p>
<pre>onConfigGetLatestDocClick: function getLatestDoc_onConfigGetLatestDocClick(e)
{
  var actionUrl = Alfresco.constants.URL_SERVICECONTEXT +
    "modules/someco/get-latest-doc/config/" +
    encodeURIComponent(this.options.componentId);</pre>
<p>The onConfigGetLatestDocClick method does two things: (a) It defines the dialog that gets displayed when someone configures the dashlet and (b) it defines field validation for the fields on the configure dialog. Here is the dialog definition part:</p>
<p><script src="http://pastebin.com/embed_js.php?i=P71A84F7"></script></p>
<p>That templateUrl is where the SimpleDialog module should find the form to use. It looks just like the actionUrl and it is. The actionUrl will be a POST while the templateUrl will be a GET.</p>
<p>The getLatestDoc_onConfig_callback function will be invoked when the configuration form is successfully posted to the config web script. That response object contains the response from that web script, which, in this case, we are returning as JSON.</p>
<p>Here is the part of the method that defines field validation:</p>
<p><script src="http://pastebin.com/embed_js.php?i=DB8XM3nf"></script></p>
<p>This function makes the title mandatory. Note the final two assignments. These are hooking up buttons on the config dialog to events in this client-side component. So, when someone clicks &#8220;select path&#8221; the folder picker will be launched and when someone clicks &#8220;clear path&#8221; the selected path will be reset.</p>
<p>Then, the final part of the method simply displays the dialog:</p>
<pre>this.configDialog.setOptions(
{
  actionUrl: actionUrl,
  siteId: this.options.siteId,
  containerId: this.options.containerId
}).show();
},</pre>
<p>So when this method is invoked, Alfresco&#8217;s out-of-the-box SimpleDialog component is used to display a pop-up dialog. The dialog will contain the form that we return in the config&#8217;s GET web script, and when the user saves the values, the values will be POSTed to the config web script.</p>
<p>As part of the configuration, the user needs to specify a folder path. Alfresco already ships with a folder picker&#8211;there is no need to code one from scratch.  The onSelectFilterPath method sets that up:</p>
<p><script src="http://pastebin.com/embed_js.php?i=jVCsz7By"></script></p>
<p>This method uses the out-of-the-box DoclibGlobalFolder to present a tabbed dialog of folders for the user to navigate and pick. When someone selects a folder, it throws a &#8220;folderSelected&#8221; event which this code listens for. When it hears it, it grabs the selected folder and nodeRef to save for later.</p>
<p>I&#8217;m not sure why Rik, my partner in crime for this little example, chose to append the selected path to the end of the nodeRef with a pipe. It could easily be stored in its own property.</p>
<p>Now, at this point, a logical question in your mind might be, &#8220;SimpleDialog and DoclibGlobalFolder look generally useful. How do I find out more about those and other goodies that might be available to my client-side JavaScript in Share?&#8221;. The answer is JSDoc. The Share Extras project has generated the JSDoc for all client-side JavaScript in Alfresco. The index for Alfresco 4.0.d lives <a title="Google Code: Share Extras JSDoc" href="http://sharextras.org/jsdoc/share/community-4.0.d/index.html">here</a>, the doc for DoclibGlobalFolder lives <a title="Google Code: Share Extras JSDoc" href="http://sharextras.org/jsdoc/share/community-4.0.d/symbols/Alfresco.module.DoclibGlobalFolder.html">here</a>, and the doc for SimpleDialog lives <a title="Google Code: Share Extras JSDoc" href="http://sharextras.org/jsdoc/share/community-4.0.d/symbols/Alfresco.module.SimpleDialog.html">here</a>.</p>
<p>With this client-side JavaScript in place, the &#8220;configure&#8221; link can now be clicked, but the SimpleDialog will be looking for a config web script that doesn&#8217;t exist yet. That&#8217;s the last step.</p>
<p><strong>Write a Share-tier web script to handle config</strong></p>
<p>In the previous section you saw that the SimpleDialog needs two web scripts: One returns a form that is rendered in the dialog. The other is the web script that dialog will POST to. These web scripts are Share tier web scripts, so they live in config/alfresco/site-webscripts. The com/someco package structure is used to keep code separate from other add-ons, and, by convention, web scripts that aren&#8217;t surf components go under &#8220;modules&#8221;. Under that I&#8217;m using &#8220;getLatestDoc&#8221; to group web scripts related to that and &#8220;config&#8221; below that to identify the purpose of these web scripts.</p>
<p>First, the GET web script. It&#8217;s kind of boring. There is no controller at all. The web script consists only of a FreeMarker view, a descriptor, and some properties files to localize the labels on the form. If you look at the view, <a title="Google Code: Get Latest Document Example - config-get-latest-doc.get.html.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/modules/getLatestDoc/config/config-get-latest-doc.get.html.ftl">config-get-latest-doc.get.html.ftl</a>, you&#8217;ll see what I mean. It doesn&#8217;t even need any client-side JavaScript&#8211;the buttons were hooked up to methods on the GetLatestDoc component in the prior step.</p>
<p>Next, the POST web script. The SimpleDialog component will be sending JSON representing the form data to this web script. Because it is sending JSON, the web script controller is named <a title="Google Code: Get Latest Document Example - config-get-latest-doc.post.json.js" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/modules/getLatestDoc/config/config-get-latest-doc.post.json.js">config-get-latest-doc.post.json.js</a>. That extra little &#8220;.json&#8221; bit gives me access to the form data in a &#8220;json&#8221; root-scoped object so I don&#8217;t have to fool around with eval.</p>
<p>The logic itself is pretty simple:</p>
<pre>var c = sitedata.getComponent(url.templateArgs.componentId);
var saveValue = function(name, value)
{
  c.properties[name] = value;
  model[name] = value;
}
saveValue("title", String(json.get("title")));
saveValue("filterPath", String(json.get("filterPath")));
saveValue("filterPathView", String(json.get("filterPath")).split("|")[1]);
c.save();</pre>
<p>What&#8217;s going on here? First, the controller grabs a handle to a component using a componentId. The component ID was passed in as an argument by the client-side JavaScript component that told the SimpleDialog which action URL to use. The client-side JavaScript component got the component ID when the dashlet&#8217;s view (<a title="Google Code: Get Latest Document Example - get-latest-doc.get.html.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/2.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.html.ftl">get-latest-doc.get.html.ftl</a>) instantiated it. So this component is actually the web script that renders the Get Latest Document dashlet.</p>
<p>The saveValue function is just a little helper that sets the properties on the component and the model in one call.</p>
<p>So, given the &#8220;json&#8221; object that is being passed in from the configuration dialog, all that needs to be done is to read those form field values out of the JSON and stick them onto the component properties and the model.</p>
<p>Now, when the dashlet&#8217;s web script is invoked, the framework will pass those properties to it via the args array. If the dashlet&#8217;s controller sees the values in the args array, it uses those, otherwise, it uses what it finds in the XML configuration.</p>
<p><strong>Deploy and Test</strong></p>
<p>You can deploy the example by running &#8220;ant hotcopy-tomcat-jar&#8221;. If you already deployed 1.0 of the code and you are running two Tomcats, you won&#8217;t have to restart your repository tier, but you will have to restart your Share tier. Then, go into a site and add the dashlet to the site dashboard. If you click the pencil icon on the dashlet&#8217;s title bar, you should see the configuration form pop up:<br />
<img class="alignnone" title="Screenshot: Get Latest Document Dashlet Configuration" src="http://ecmarchitect.com/images/get-latest-doc-config.png" alt="" width="399" height="175" /></p>
<p>If you then click &#8220;Select Path&#8221; the folder browser should be displayed:<br />
<img class="alignnone" title="Screenshot: Get Latest Document Dashlet Configuration Folder Picker" src="http://ecmarchitect.com/images/get-latest-doc-config-folders-sm.png" alt="" width="395" height="191" /></p>
<p>On clicking &#8220;OK&#8221; the new configuration values will be persisted, but you&#8217;ll have to refresh the page to see them take effect. Of course you could modify the example further to move the rendering of the metadata to the client-side such that when the configuration data is saved it immediately refreshes the dashlet content, but I&#8217;ll save that for another time.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2012/05/15/1599/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Alfresco Example: Share Dashlets that Retrieve Data from the Repository</title>
		<link>http://ecmarchitect.com/archives/2012/05/08/1592</link>
		<comments>http://ecmarchitect.com/archives/2012/05/08/1592#comments</comments>
		<pubDate>Tue, 08 May 2012 07:07:56 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Alfresco Customization]]></category>
		<category><![CDATA[Dashlets]]></category>
		<category><![CDATA[Example]]></category>
		<category><![CDATA[Share Customization]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1592</guid>
		<description><![CDATA[If you need to learn how to write an Alfresco Share dashlet, hopefully you&#8217;ve already worked through the Hello World dashlets in the Share Extras project on Google Code. The rest of the dashlets in that project are either &#8220;mash-up&#8221; style dashlets that read data from third-party sites or dashlets that work with data from [...]]]></description>
			<content:encoded><![CDATA[<p>If you need to learn how to write an <a href="http://www.alfresco.com">Alfresco</a> Share dashlet, hopefully you&#8217;ve already worked through the Hello World dashlets in the <a title="Google Code: Share Extras" href="http://code.google.com/p/share-extras/">Share Extras project on Google Code</a>. The rest of the dashlets in that project are either &#8220;mash-up&#8221; style dashlets that read data from third-party sites or dashlets that work with data from the Alfresco repository tier, but are of moderate to high complexity. What I think is missing is a dashlet that goes one small step beyond Hello World to show how to read and display a simple set of data from the repository tier. In this post I show you how to do just that.</p>
<p>I created the example I&#8217;m going to walk you through in response to a question someone posted in the forum. They asked, &#8220;How can I write a dashlet that will show basic metadata from the most recently-modified document in a given folder?&#8221;. I think this is something we haven&#8217;t explained to people&#8211;we show Hello World and we show dashlets from the other end of the spectrum, but nothing to illustrate this basic pattern. So I wrote a quick-and-dirty example. Fellow forum user, RikTaminaars, also got in on the act, making the dashlet configurable, which I&#8217;ll show in a subsequent post.</p>
<p>In this post, I&#8217;ll create the most basic implementation possible. The result is a simple dashlet that reads a path from a configuration file, asks the repository for the most recently modified document in that folder, then displays some basic metadata. In my next post, I&#8217;ll add to it by making the folder path configurable within the user interface.</p>
<p><strong>Developer Setup</strong></p>
<p>First, a bit about my setup. I&#8217;m running Alfresco 4.0.d Community, WAR distribution, Lucene for search, Tomcat application server, and MySQL for the database. I&#8217;m using Eclipse Indigo, but this implementation requires no compiled code. For organizing the project files I used the <a title="Google Code: Share Extras Sample Dashlet source" href="http://code.google.com/p/share-extras/source/browse/#svn%2Ftrunk%2FSample%20Dashlet">Sample Dashlet</a> project structure from Share Extras. I&#8217;m using the Share Extras Ant build script to deploy my code from my project folder structure to the Alfresco and Share web applications.</p>
<p>When I&#8217;m doing Share customizations I use two Tomcats&#8211;one for the repository tier and one for the Share tier. This helps me avoid making assumptions about where my Alfresco and Share web applications are running and it speeds up my development because my Share Tomcat restarts very quickly. To get the Share Extras build script to work with a two Tomcats setup, I&#8217;ve added the following to a build.properties file in my home directory:</p>
<p><code>tomcat.home=/opt/apache/tomcat/apache-tomcat-6.0.32<br />
tomcat.share.home=/opt/apache/tomcat81/apache-tomcat-6.0.32<br />
tomcat.share.url=http://localhost:8081</code></p>
<p>Now the build script will know to put the deployed code in each of my Tomcat servers.</p>
<p><strong>The Data Web Script/Presentation Web Script Pattern</strong></p>
<p>The Alfresco repository runs in a web application called &#8220;alfresco&#8221; while the Share user interface runs in a separate web application called &#8220;share&#8221;. Each of these two web applications has its own web script engine. If you aren&#8217;t familiar with web scripts, take a look at <a title="ecmarchitect.com Introduction to Web Scripts Tutorial" href="http://ecmarchitect.com/images/articles/alfresco-webscripts/web-script-article.pdf">this tutorial</a>, then come back, or just accept the high-level notion that web scripts are an MVC framework used to bind URLs to code and that web scripts can run on the repository tier as well as the Share tier.</p>
<p>Share dashlets&#8211;the &#8220;boxes&#8221; of content that display in the Share user interface&#8211;are just web scripts that return HTML. In fact, everything you see in Share is ultimately a web script. When a dashlet needs to display data from the Alfresco repository, it makes a call over HTTP to invoke a web script running on the repository tier. The repository tier web script responds, usually with JSON. The dashlet gets what it needs from the JSON, then renders outputs HTML to be rendered as part of the page. Because the repository tier web script responds with raw data, you may see these referred to as &#8220;data web scripts&#8221;.</p>
<p>In the previous paragraph, I was purposefully vague about where the request to the repository tier is made. That&#8217;s because there are two options. The first option, and the one we&#8217;ll use in this example, is to make the call from the dashlet&#8217;s web script controller. This might be called &#8220;the server-side option&#8221; because once the dashlet&#8217;s view is rendered, it has all of the data it needs&#8211;no further calls are needed.</p>
<p>The second option is to make AJAX calls from the client browser. To avoid cross-site scripting limitations, these calls typically go through a proxy that lives in the Share application, but their ultimate destination is the repository tier.</p>
<p><img class="alignnone" title="Two tier web scripts" src="http://ecmarchitect.com/images/two-tier-web-scripts.png" alt="Two tier web scripts" width="321" height="412" /></p>
<p>You may be wondering about the details of making these remote connections. It&#8217;s all taken care of for you. Regardless of whether you make the remote invocation of the repository web script from the Share tier or through AJAX in the browser, all you need to specify is the URL you want to invoke and the framework handles everything else.</p>
<p><strong>Show Me the Code: Repository Tier</strong></p>
<p>Let&#8217;s walk through <a title="Google Code: Get Latest Document Example" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/#svn%2Ftags%2F1.0">the code</a>. I&#8217;ll start with the repository tier. Recall that the requirements are to find the most recently modified document in a given folder and return its metadata. There may be out-of-the-box web scripts that could be leveraged to return the most recently modified document, but this is actually very simple to do and I don&#8217;t want to have to worry about those out-of-the-box web scripts changing in some subsequent release, so I&#8217;m implementing this myself.</p>
<p>Using the Share Extras project layout, repository tier web scripts reside under config/alfresco/templates/webscripts. The best practice is to place your web script files in a package structure beneath that, so I am using &#8220;com/someco/components/dashlets&#8221; for my package structure. Yours would be different.</p>
<p>The first thing to think about is what the URL should look like. I want it to be unique and I need it to accept an argument. The argument is the folder path to be searched. For this example, my URL will look like this:</p>
<p><code>/someco/get-latest-doc?filterPathView={filterPathView}</code></p>
<p>If you&#8217;ve worked with web scripts, you know that a &#8220;path&#8221; pattern could be followed instead of a &#8220;query string&#8221; pattern but it doesn&#8217;t really matter for this example. The descriptor, <a title="Google Code: Get Latest Document Example - get-latest-doc.desc.xml" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/templates/webscripts/com/someco/getLatestDoc/get-latest-doc.get.desc.xml">get-latest-doc.get.desc.xml</a>, declares the URL and other details about authentication and transaction requirements.</p>
<p>The controller performs the search and populates the model with data. This is a GET, so the controller is named <a title="Google Code: Get Latest Document Example - get-latest-doc.get.js" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/templates/webscripts/com/someco/getLatestDoc/get-latest-doc.get.js">get-latest-doc.get.js</a>. Look at the source for the complete listing&#8211;I&#8217;ll just hit the high points.</p>
<p>First, I grab a handle to the folder represented by the path argument:</p>
<p><code>var folder = companyhome.childByNamePath(args.filterPathView);</code></p>
<p>Then I use the childFileFolders() method to get the most recently modified file and stick that in the model:</p>
<p><code>var results = folder.childFileFolders(true, false, 'cm:folder', 0, 1, 0, 'cm:modified', false, null);<br />
model.latestDoc = files[0];</code></p>
<p>Obviously, I could have used a search here, but this method is quite handy. It must be new (thanks, Rik!).</p>
<p>The view then takes the data in the model and returns some of the metadata as JSON:</p>
<p><code>&lt;#macro dateFormat date&gt;${date?string("dd MMM yyyy HH:mm:ss 'GMT'Z '('zzz')'")}&lt;/#macro&gt;<br />
&lt;#escape x as jsonUtils.encodeJSONString(x)&gt;<br />
{<br />
"nodeRef": "${latestDoc.nodeRef}",<br />
"name": "${latestDoc.properties.name}",<br />
"title": "${latestDoc.properties.title!}",<br />
"description": "${latestDoc.properties.description!}",<br />
"created": "&lt;@dateFormat latestDoc.properties.created /&gt;",<br />
"modified": "&lt;@dateFormat latestDoc.properties.modified /&gt;"<br />
}<br />
&lt;/#escape&gt;</code></p>
<p>Note the exclamation points at the end of the title and description. That will keep the template from blowing up if those values are not set.</p>
<p>That&#8217;s all there is to the repository tier. If you want to add additional metadata, custom properties, or data from elsewhere in the repo, it is easy to do that.</p>
<p>At this point you should be able to deploy (run ant hotcopy-tomcat-jar, for example) and test the repository tier web script by pointing your browser to: http://localhost:8080/alfresco/s/someco/get-latest-doc?filterPathView=/Some/Folder/Path</p>
<p><strong>Show Me the Code: Share Tier</strong></p>
<p>Okay, the back-end is ready to return the data I need. Now for the front-end. The Share Extras project structure puts front-end web scripts in config/alfresco/site-webscripts so following a similar pattern as I did for the repository-tier web scripts, I put my share-tier web scripts into the site-webscripts folder under com/someco/components/dashlets.</p>
<p>The descriptor, <a title="Google Code: Get Latest Document Example - get-latest-doc.get.desc.xml" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.desc.xml">get-latest-doc.get.desc.xml</a>, declares a URL for the dashlet and specifies the &#8220;dashlet&#8221; family. This will make the dashlet appear in the &#8220;add dashlets&#8221; menu for both the global dashboard and the site dashboard.</p>
<p>I want to make the folder path configurable, so I created <a title="Google Code: Get Latest Document Example - get-latest-doc.get.config.xml" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.config.xml">get-latest-doc.get.config.xml</a> and used it to specify a title for the dashlet and the default folder path. I&#8217;ll read in this configuration from the controller.</p>
<p>As I mentioned, we&#8217;re doing everything server-side in this example, so the controller needs to call the web script I deployed to the repository tier to retrieve the JSON. Then it can put that object into the model and let the view format it as it sees fit. The controller lives in <a title="Google Code: Get Latest Document Example - get-latest-doc.get.js" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.js">get-latest-doc.get.js</a> and starts out by reading the configuration XML:</p>
<p><script src="http://pastebin.com/embed_js.php?i=6MAMpGVK"></script></p>
<p>Then it uses the built-in &#8220;remote&#8221; object to invoke the repository tier web script and puts the result into the model:</p>
<p><script src="http://pastebin.com/embed_js.php?i=9hKgsbgM"></script></p>
<p>Note that the URL starts with my web script&#8217;s URL&#8211;it includes nothing about where to find the Alfresco repository. That&#8217;s because the &#8220;end point&#8221; is declared in Share&#8217;s configuration (to be precise, it is actually Surf configuration, the framework on which Share is built) and the remote object uses the &#8220;alfresco&#8221; endpoint by default. I don&#8217;t see it used very often, but you can actually define your own end points and invoke them using the remote object.</p>
<p>The last part of this example is the HTML markup for the dashlet itself. The first half of <a title="Google Code: Get Latest Document Example - get-latest-doc.get.html.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.html.ftl">get-latest-doc.get.html.ftl</a> is boilerplate that has to do with the dashlet resizer. For this example, the length of the list is static&#8211;it will only ever have one entry. So the resizer is kind of overkill. The bottom half is where the markup for the document&#8217;s metadata lives:</p>
<p><script src="http://pastebin.com/embed_js.php?i=0ns2sG6Y"></script></p>
<p>If you&#8217;ve already worked with web scripts, you know that <a title="Google Code: Get Latest Document Example - get-latest-doc.get.head.ftl" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.head.ftl">get-latest-doc.get.head.ftl</a> is used to place resources into &#8220;&lt;head&gt;&#8221;. In this case, that is a CSS file used to style our dashlet and client-side JavaScript used by the resizer. The strings used in the dashlet are localized through the <a title="Google Code: Get Latest Document Example - get-latest-doc.get.properties" href="http://code.google.com/p/alfresco-get-latest-document/source/browse/tags/1.0/config/alfresco/site-webscripts/com/someco/components/dashlets/get-latest-doc.get.properties">get-latest-doc.get.properties file</a>.</p>
<p><strong>Test It Out</strong></p>
<p>You should be able to test this out yourself by creating a folder anywhere in the repository, adding one or more files to the folder, specifying that folder path in the get-latest-doc.get.config.xml file, and then using &#8220;ant deploy hotcopy-tomcat-jar&#8221; to deploy the customizations to the alfresco and share web applications. You&#8217;ll need to restart Tomcat to pick up the new JAR (both Tomcats if you are running two). Then, login and click &#8220;Customize Dashboard&#8221; to add the &#8220;Get Latest Document&#8221; dashlet to your dashboard. It should look something like this:</p>
<p><img class="alignnone" title="Screenshot: Get Latest Document Dashlet" src="http://ecmarchitect.com/images/get-latest-doc.png" alt="Screenshot: Get Latest Document Dashlet" width="357" height="118" /></p>
<p><strong>Summary</strong></p>
<p>This is a simple example, but it is powerful because it is the same pattern used throughout Share: A dashlet needs data from the repository tier, so its web script controller uses the &#8220;remote&#8221; object to call a web script on the repository tier. The repository tier web script returns JSON which the Share tier web script then formats into a nice looking dashlet.</p>
<p>In my next post, I&#8217;ll show how to make the folder path configurable, so that you can change it at run-time with a folder picker in the Share user interface instead of editing the config XML.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2012/05/08/1592/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Alfresco tutorial: Advanced Workflows using Activiti</title>
		<link>http://ecmarchitect.com/archives/2012/02/20/1552</link>
		<comments>http://ecmarchitect.com/archives/2012/02/20/1552#comments</comments>
		<pubDate>Mon, 20 Feb 2012 19:59:35 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Activiti]]></category>
		<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[jBPM]]></category>
		<category><![CDATA[Workflow]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1552</guid>
		<description><![CDATA[In 2007, I wrote a tutorial on Alfresco&#8217;s advanced workflows which I later used as the basis for the workflow chapter in the Alfresco Developer Guide. It showed examples using jBPM and the old Alfresco Explorer web client. Then, in April of 2011 I posted a short article comparing Alfresco workflows built with jBPM to [...]]]></description>
			<content:encoded><![CDATA[<p>In 2007, I wrote a <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Advanced Workflows, 1st Edition" href="http://ecmarchitect.com/archives/2007/11/19/785">tutorial</a> on Alfresco&#8217;s advanced workflows which I later used as the basis for the workflow chapter in the Alfresco Developer Guide. It showed examples using jBPM and the old Alfresco Explorer web client.</p>
<p>Then, in April of 2011 I <a title="ecmarchitect.com blog post: Workflow examples with Activiti" href="http://ecmarchitect.com/archives/2011/04/27/1357">posted</a> a short article comparing Alfresco workflows built with jBPM to the same workflows built with Activiti, the new advanced workflow engine embedded in Alfresco 4. The article provided a quick glimpse into the new Activiti engine aimed at those who had heard about the Alfresco-sponsored project.</p>
<p>Today I&#8217;m making available the <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Advanced Workflows, 2nd Edition" href="http://ecmarchitect.com/images/articles/alfresco-workflow/advanced-workflow-article-2ed.pdf">2nd edition of the advanced workflow tutorial</a>. It combines the SomeCo whitepaper example from 2007 with a few hello world examples to show you how to use the <a title="Activiti Home Page" href="http://activiti.org">Activiti</a> Process Designer Eclipse plug-in and the Activiti engine to design and run the example workflows, including how to configure your workflows in the Alfresco Share web client.</p>
<p>The accompanying <a title="ecmarchitect.com Alfresco Developer Series Tutorial Source Code: Advanced Workflows" href="http://ecmarchitect.com/images/articles/alfresco-workflow/alfresco-workflow-tutorial.zip">source code</a> builds on the workflow model and associated customizations created in the 2nd editions of the <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Working with Custom Content Types, 2nd Edition" href="http://ecmarchitect.com/archives/2012/01/09/1509">custom content types</a> and <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Creating Custom Actions, 2nd Edition" href="http://ecmarchitect.com/images/articles/alfresco-actions/actions-article-2ed.pdf">custom actions tutorials</a>.</p>
<p>Special thanks go to <a title="Joram Barrez blog" href="http://www.jorambarrez.be/blog/">Joram Barrez</a> and <a title="Tijs Rademakers blog" href="http://bpmn20inaction.blogspot.com/">Tijs Rademakers</a> for reviewing the tutorial and providing valuable feedback. Both are on the Activiti team. In fact, Tijs has been working on an Activiti book called <a title="Manning: Activiti in Action" href="http://www.manning.com/rademakers2/">Activiti in Action</a> which should be out soon, so keep an eye out for that.</p>
<p>Anyway, take a look and let me know what you think.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2012/02/20/1552/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Alfresco Tutorial: Custom actions including Share configuration</title>
		<link>http://ecmarchitect.com/archives/2012/01/23/1519</link>
		<comments>http://ecmarchitect.com/archives/2012/01/23/1519#comments</comments>
		<pubDate>Mon, 23 Jan 2012 16:52:43 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Content Management]]></category>
		<category><![CDATA[Alfresco Share]]></category>
		<category><![CDATA[Alfresco Tutorial]]></category>
		<category><![CDATA[Custom Actions]]></category>
		<category><![CDATA[Share Customization]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1519</guid>
		<description><![CDATA[I&#8217;ve published a revision of my original Alfresco custom actions tutorial. The second edition greatly expands on the first by adding a UI action example. The original included only a rule action example. Just like the second edition of the content types tutorial, I&#8217;ve added instructions on how to configure the actions in Alfresco Share. [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve published a revision of my original <a href="http://www.alfresco.com" alt="Alfresco Home">Alfresco</a> custom actions tutorial. The <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Creating Custom Actions" href="http://ecmarchitect.com/images/articles/alfresco-actions/actions-article-2ed.pdf">second edition</a> greatly expands on the first by adding a UI action example. The original included only a rule action example. Just like the second edition of the content types tutorial, I&#8217;ve added instructions on how to configure the actions in Alfresco Share. The Alfresco Explorer steps are still there&#8211;they&#8217;ve been moved to the Appendix.</p>
<p><a title="ecmarchitect.com Alfresco Developer Series Tutorial Source Code: Creating Custom Actions" href="http://ecmarchitect.com/images/articles/alfresco-actions/alfresco-actions-tutorial.zip">The code</a> that accompanies the tutorial builds on the <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Working with Custom Content Types" href="http://ecmarchitect.com/archives/2012/01/09/1509">content types tutorial</a>, so it includes the SomeCo content model and the user interface configuration needed to expose that to the Alfresco Share and Alfresco Explorer user interface.</p>
<p>This should be helpful to anyone who read the first edition who now wants to learn how to do the same thing using Alfresco Share, including some of the new extension points available in Alfresco 4.</p>
<p>Take a look and tell me what you think.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2012/01/23/1519/feed</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Alfresco tutorial: Custom content types including Share config and CMIS</title>
		<link>http://ecmarchitect.com/archives/2012/01/09/1509</link>
		<comments>http://ecmarchitect.com/archives/2012/01/09/1509#comments</comments>
		<pubDate>Mon, 09 Jan 2012 20:44:57 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Alfresco Tutorial]]></category>
		<category><![CDATA[Content Model]]></category>
		<category><![CDATA[OpenCMIS]]></category>
		<category><![CDATA[Share Configuration]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1509</guid>
		<description><![CDATA[It is hard to believe that the original version of my &#8220;Working With Custom Content Types&#8221; tutorial for Alfresco is almost five years old. That page has had over 37,000 unique visits since it was posted. It makes sense that it would be popular&#8211;creating a content model, exposing it to the user interface, and then [...]]]></description>
			<content:encoded><![CDATA[<p>It is hard to believe that the <a title="ecmarchitect.com blog post: Original content types tutorial" href="http://ecmarchitect.com/archives/2007/06/09/756">original version</a> of my &#8220;Working With Custom Content Types&#8221; tutorial for Alfresco is almost five years old. That page has had over 37,000 unique visits since it was posted. It makes sense that it would be popular&#8211;creating a content model, exposing it to the user interface, and then performing CRUD functions against the repository through code are the first steps for most Alfresco development projects.</p>
<p>The fundamentals of content modeling haven&#8217;t changed since 2007, but since the original tutorial was posted the Alfresco Share web client has replaced Alfresco Explorer as the preferred user interface and the Content Management Interoperability Services (CMIS) API has become the first choice for writing remote code against the repository. That, combined with the influx of newcomers to the platform and a continued demand for how-to&#8217;s on the basics motivated me to revise the tutorial.</p>
<p>The <a title="ecmarchitect.com Alfresco Developer Series Tutorial: Working with Custom Content Types" href="http://ecmarchitect.com/images/articles/alfresco-content/content-article-2ed.pdf">second edition</a> moves the Alfresco Explorer configuration to the Appendix and replaces it with steps for doing the same thing in Alfresco Share. I also moved the Java Web Services API to the Appendix and replaced that with Java examples that leverage the Apache Chemistry OpenCMIS API to create, update, query, and delete content in the repository. I&#8217;m executing the same queries as the first edition, just implemented using CMIS, so if you want to compare Lucene queries to CMIS Query Language, this is one place to do it.</p>
<p>I tested the document and the <a title="ecmarchitect.com Alfresco Developer Series Tutorial Source Code: Working with Custom Content Types" href="http://ecmarchitect.com/images/articles/alfresco-content/alfresco-content-tutorial-project.zip">code</a> against Alfresco 4.0.c Community. It may work on earlier versions, but no promises. It will probably work on Alfresco 4.0 Enterprise when it becomes available.</p>
<p>My goal is to revise the rest of the <a title="ecmarchitect.com Alfresco Developer Series Tutorials" href="http://ecmarchitect.com/alfresco-developer-series">Alfresco Developer Series</a> tutorials for Alfresco 4 over the next month or two so stay tuned.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2012/01/09/1509/feed</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>New Tutorial: Getting Started with CMIS</title>
		<link>http://ecmarchitect.com/archives/2009/11/23/1094</link>
		<comments>http://ecmarchitect.com/archives/2009/11/23/1094#comments</comments>
		<pubDate>Mon, 23 Nov 2009 22:52:42 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[CMIS]]></category>
		<category><![CDATA[Content Management]]></category>
		<category><![CDATA[General]]></category>
		<category><![CDATA[Apache Abdera]]></category>
		<category><![CDATA[Apache Chemistry]]></category>
		<category><![CDATA[AtomPub Binding]]></category>
		<category><![CDATA[Examples]]></category>
		<category><![CDATA[Optaros]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1094</guid>
		<description><![CDATA[I&#8217;ve written a new tutorial on the proposed Content Management Interoperability Services (CMIS) standard called, &#8220;Getting Started with CMIS&#8220;. The tutorial first takes you through an overview of the specification. Then, I do several examples. The examples start out using curl to make GET, PUT, POST, and DELETE calls against Alfresco to perform CRUD functions [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written a new tutorial on the proposed <a title="CMIS Committee Home Page at OASIS" href="http://www.oasis-open.org/committees/cmis" target="_blank">Content Management Interoperability Services (CMIS)</a> standard called, &#8220;<a title="ecmarchitect.com tutorial: Getting Started with CMIS" href="http://ecmarchitect.com/images/articles/cmis/cmis-article.pdf" target="_self">Getting Started with CMIS</a>&#8220;. The tutorial first takes you through an overview of the specification. Then, I do several examples. The examples start out using curl to make GET, PUT, POST, and DELETE calls against Alfresco to perform CRUD functions on folders, documents, and relationships in the repository. If you&#8217;ve been dabbling with CMIS and you&#8217;ve struggled to find examples, particularly of POSTs, here you go.</p>
<p>I used Alfresco Community built from head, but yesterday, Alfresco pushed a new Community release that supports CMIS 1.0 Committee Draft 04 so you can <a title="Alfresco Community Download Page" href="http://wiki.alfresco.com/wiki/Download_Community_Edition" target="_blank">download</a> that, use the <a title="Alfresco hosted CMIS repository" href="http://cmis.alfresco.com" target="_blank">hosted Alfresco CMIS repository</a>, or spin up an <a title="Alfresco wiki: Official Amazon EC2 image" href="http://wiki.alfresco.com/wiki/EC2" target="_blank">EC2 image</a> (once Luis gets it updated with the new Community release). If you don&#8217;t want to use Alfresco you should be able to use any CMIS repository that supports 1.0cd04. I tried some, but not all, of the command-line examples against the Apache <a title="Apache Chemistry Home" href="http://incubator.apache.org/chemistry/" target="_blank">Chemistry</a> test server.</p>
<p>Once you&#8217;ve felt both the joy and the pain of talking directly to the CMIS AtomPub Binding, I take you through some very short examples using JavaScript and Java. For Java I show Apache <a title="Apache Abdera Home" href="http://abdera.apache.org/" target="_blank">Abdera</a>, Apache Chemistry, and the Apache Chemistry TCK.</p>
<p>For the Chemistry TCK stuff, I&#8217;m using Alfresco&#8217;s <a title="Alfresco wiki: CMIS Maven Toolkit" href="http://wiki.alfresco.com/wiki/CMIS_Maven_Toolkit" target="_blank">CMIS Maven Toolkit</a> which <a title="Gabrielle Columbro's Blog" href="http://mindthegab.com/" target="_blank">Gabriele Columbro</a> and <a title="Richard McKnight's blog" href="http://oldschooltechie.com/" target="_blank">Richard McKnight</a> put together. That inspired me to do <a title="CMIS article source code archive" href="http://ecmarchitect.com/images/cmis-article-code.zip">my examples</a> with Maven as well (plus, it&#8217;s practical&#8211;the Abdera and Chemistry clients have a lot of dependencies, and using Maven meant I didn&#8217;t have to chase any of those down).</p>
<p>So take a look at the tutorial, try out the examples with your favorite CMIS 1.0 repo, and let me know what you think. If you like it, pass it along to a friend. As with past tutorials, I&#8217;ve released it under <a title="Creative Commons Attribution-Share Alike 3.0" href="http://creativecommons.org/licenses/by-sa/3.0/" target="_blank">Creative Commons Attribution-Share Alike</a>.</p>
<p>[Updated to correct typo with Gabriele's name. Sorry, Gab!]</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2009/11/23/1094/feed</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>Alfresco Developer Guide source reorg and 3.2 Community update</title>
		<link>http://ecmarchitect.com/archives/2009/07/30/1023</link>
		<comments>http://ecmarchitect.com/archives/2009/07/30/1023#comments</comments>
		<pubDate>Thu, 30 Jul 2009 05:50:38 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Book]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Content Management]]></category>
		<category><![CDATA[Alfresco Developer Guide]]></category>
		<category><![CDATA[Example]]></category>
		<category><![CDATA[Optaros]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=1023</guid>
		<description><![CDATA[[UPDATE: Added a link to the source code that works with 3.2 Enterprise] I originally wrote the Alfresco Developer Guide source code for Alfresco 2.2 Enterprise and Alfresco 3 Labs. The code was pretty much the same regardless of which one you were running. For things that did happen to be different, I handled those [...]]]></description>
			<content:encoded><![CDATA[<p>[UPDATE: Added a link to the source code that works with 3.2 Enterprise]</p>
<p>I originally wrote the <a title="Buy the Alfresco Developer Guide" href="http://www.packtpub.com/alfresco-developer-guide/book/mid/080509wz4lps" target="_blank">Alfresco Developer Guide</a> source code for Alfresco 2.2 Enterprise and Alfresco 3 Labs. The code was pretty much the same regardless of which one you were running. For things that did happen to be different, I handled those with separate projects: one for community-specific stuff and one for enterprise-specific stuff. This was pretty much limited to minor web script differences for the &#8220;client extensions&#8221; projects and LDAP configuration differences for the &#8220;server extension&#8221; project.</p>
<p>With the release of 3.2 Community, I realized:</p>
<ul>
<li>The number of different flavors of Alfresco any given reader might be running are going up, not down. Who knows when 2.2 Enterprise will be sunset.</li>
<li>It is no longer as easy as &#8220;Enterprise&#8221; versus &#8220;Labs/Community&#8221; because multiple releases of the same flavor are prevalent (2.2E, 3.0E, and 3.1E, for example).</li>
<li>Tagging my code in Subversion by Chapter alone is no longer enough&#8211;I need to tag by Chapter and by Alfresco version.</li>
<li>Sending the publisher the code one chapter at-a-time and expecting them to manage updates and deciding how to organize all of the chapter code was a bad idea.</li>
</ul>
<p>So, I&#8217;ve done some work to make this better (reorg the projects, restructure the download files). I&#8217;ve also tested the example code from each chapter against the latest service packs for all releases since 2.2 Enterprise. That includes making some small updates to get the examples running on 3.2 Community.</p>
<p>You can now download either all of the source for every version I tested against, or, download the source that works for a specific version. It may take the official download site at Packt a while to get the new files, so here are links to download them from my site:</p>
<p>Alfresco Developer Guide example source code for&#8230;</p>
<ul>
<li>Alfresco 2.2 Enterprise (~5.3 MB, <a title="Source download 2.2 enterprise" href="http://ecmarchitect.com/images/3117_code_2.2-enterprise.zip">Download</a>)</li>
<li>Alfresco 3.0 Labs (~5.6 MB, <a title="Source download 3d Labs" href="http://ecmarchitect.com/images/3117_code_3.0-labs.zip">Download</a>)</li>
<li>Alfresco 3.0 Enterprise (~5.7 MB, <a title="Source download 3.0 enterprise" href="http://ecmarchitect.com/images/3117_code_3.0-enterprise.zip">Download</a>)</li>
<li>Alfresco 3.1 Enterprise (~5.6 MB, <a title="Source download 3.1 enterprise" href="http://ecmarchitect.com/images/3117_code_3.1-enterprise.zip">Download</a>)</li>
<li>Alfresco 3.2 Community (~5.7 MB, <a title="Source download 3.2 community" href="http://ecmarchitect.com/images/3117_code_3.2-community.zip">Download</a>)</li>
<li>Alfresco 3.2 Enterprise (~5.9 MB, <a title="Source download 3.2 enterprise" href="http://ecmarchitect.com/images/3117_code_3.2-enterprise.zip" target="_self">Download</a>)</li>
<li>All of the above, combined (~28.1 MB, <a title="Source download all versions combined" href="http://ecmarchitect.com/images/3117_code.zip">Download</a>)</li>
</ul>
<p>Hopefully this makes it easier for you to grab only what you need and makes it clear that each Eclipse project contains only what&#8217;s needed to work with that version of Alfresco. Deployment is easier too. Most of the time, it&#8217;s just the &#8220;someco-client-extensions&#8221; project that you deploy.</p>
<p>Now that I&#8217;ve got everything structured like I want it, as new versions of Alfresco are released, it should be much easier to keep up.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2009/07/30/1023/feed</wfw:commentRss>
		<slash:comments>33</slash:comments>
		</item>
		<item>
		<title>Keeping your Alfresco web scripts DRY</title>
		<link>http://ecmarchitect.com/archives/2009/06/01/994</link>
		<comments>http://ecmarchitect.com/archives/2009/06/01/994#comments</comments>
		<pubDate>Mon, 01 Jun 2009 16:05:22 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Alfresco Developer Guide]]></category>
		<category><![CDATA[Best Practices]]></category>
		<category><![CDATA[Optaros]]></category>
		<category><![CDATA[Tips]]></category>
		<category><![CDATA[Web Scripts]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=994</guid>
		<description><![CDATA[One of my teammates thoroughly drenched our under-the-desk development server with a giant cup of coffee once. Somehow, disaster was avoided, although the client&#8217;s carpet had a nice coffee-stain outline of the server&#8217;s footprint long after the app rolled out which afforded the rest of us endless opportunities to mercilessly haze the clumsy coder. Keeping [...]]]></description>
			<content:encoded><![CDATA[<p>One of my teammates thoroughly drenched our under-the-desk development server with a giant cup of coffee once. Somehow, disaster was avoided, although the client&#8217;s carpet had a nice coffee-stain outline of the server&#8217;s footprint long after the app rolled out which afforded the rest of us endless opportunities to mercilessly haze the clumsy coder.</p>
<p>Keeping your Alfresco web scripts DRY is actually not about making your developers use spill-proof travel mugs, or better yet, virtual machines. DRY is a coding philosophy or principle that stands for Don&#8217;t Repeat Yourself. There are all kinds of resources available that go into more detail, and the principle is more broad than simply avoiding duplicate code, but that&#8217;s what I want to focus on here.</p>
<p>There are three techniques you should be using to avoid repeating yourself when writing web scripts: web script configuration, JavaScript libraries accessed via import, and FreeMarker macros accessed via import.</p>
<p><strong>Web Script Configuration</strong></p>
<p>Added in 3.0, a web script configuration file is an XML file that contains arbitrary settings for your web script. It&#8217;s accessible from both your controller and your view. Building on the hello world web script from the <a title="Alfresco Developer Guide Book" href="http://www.packtpub.com/alfresco-developer-guide/book/mid/080509wz4lps" target="_blank">Alfresco Developer Guide</a>, you could add a configuration script named &#8220;helloworld.get.config.xml&#8221; that contained:</p>
<p><code><br />
&lt;properties&gt;<br />
&lt;title&gt;Hello World&lt;/title&gt;<br />
&lt;/properties&gt;<br />
</code></p>
<p>You could then access the &#8220;title&#8221; element from a JavaScript controller using the built-in E4X library:</p>
<p><code>var s = new XML(config.script);<br />
logger.log(s.title);<br />
</code></p>
<p>And, you could also grab the title from the FreeMarker view:</p>
<p><code>&lt;title&gt;${config.script["properties"]["title"]}&lt;/title&gt;</code></p>
<p>The web script configuration lets you separate your configuration from your controller logic. And because you can get to it from both the controller and the view, you don&#8217;t have to stick configuration info into your model.</p>
<p>This example showed a script-specific configuration, but global configuration is also possible. See the <a title="Alfresco Web Scripts Wiki" href="http://wiki.alfresco.com/wiki/Web_Scripts#Configuration" target="_blank">Alfresco Wiki Page on Web Scripts</a> for more details.</p>
<p><strong>JavaScript Import</strong></p>
<p>If your web script controllers are written in JavaScript, at some point you will find yourself writing JavaScript functions that you&#8217;d like to share across multiple web scripts. A common example is logic that builds a query string, executes the query, and returns the results. You don&#8217;t want to repeat the code that does that across multiple controllers. That&#8217;s what the import statement is for.</p>
<p>The syntax is easy:</p>
<p><code>&lt;import resource="classpath:alfresco/extension/scripts/status.js"&gt;</code></p>
<p>This needs to be the first line of your JavaScript controller file. This example shows a JavaScript library being imported from the classpath, but you can also import from the repository by name or by node reference. See the <a title="Alfresco JavaScript API Wiki" href="http://wiki.alfresco.com/wiki/3.0_JavaScript_API#Script_Files" target="_blank">Alfresco JavaScript API Wiki Page</a> for more details.</p>
<p><strong>FreeMarker Import</strong></p>
<p>Of the three this is the one I see ignored most often. Let&#8217;s take the <a title="Optaros Home Page" href="http://www.optaros.com" target="_blank">Optaros</a>-developed microblogging component for Share. Its basic data entity is called a &#8220;Status&#8221; object. So web scripts on the repository tier return JSON that might have a single Status object or a list of Status objects. That&#8217;s two different web scripts and two different views, but the difference between a list of objects and a single object is really just the list-related wrapper&#8211;in both cases, the individual Status object JSON is identical. I&#8217;ve seen people simply copy-and-paste the FreeMarker from the single-object template to the list template and then just wrap that with the list markup. <em>That&#8217;s bad.</em> If you ever change how a Status object is structured, you&#8217;ve got to change it in (at least) two places. (Or it&#8217;ll be someone that comes after you that has to do it which makes it even worse. If you aren&#8217;t following the analogy, your redundant code is the coffee stain).</p>
<p>Instead of duplicating the logic, use a FreeMarker import and define a macro that formats the object. Then you can call the macro any time you need your object formatted. Here&#8217;s how it works for Status.</p>
<p>A FreeMarker file called &#8220;status.lib.ftl&#8221; contains the macros that format Status objects. It lives with the rest of the web script files and looks like this:</p>
<p><code><br />
&lt;#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz"&gt;<br />
&lt;#--<br />
Renders a status node as a JSON object<br />
--&gt;<br />
&lt;#macro statusJSON status&gt;<br />
&lt;#escape x as jsonUtils.encodeJSONString(x)&gt;<br />
{<br />
"siteId" : "${status.properties["optStatus:siteId"]!''}",<br />
"user" : "${status.properties["optStatus:user"]!''}",<br />
"message" : "${status.properties["optStatus:message"]!''}",<br />
"prefix" : "${status.properties["optStatus:prefix"]!''}",<br />
"mood" : "${status.properties["optStatus:mood"]!''}",<br />
"complete" : ${(status.properties["optStatus:complete"]!'false')?string},<br />
"created" : "${status.properties["cm:created"]?string(datetimeformat)}",<br />
"modified" : "${status.properties["cm:modified"]?string(datetimeformat)}"<br />
}<br />
&lt;/#escape&gt;<br />
&lt;/#macro&gt;<br />
&lt;#--<br />
Renders a status node as HTML<br />
--&gt;<br />
&lt;#macro statusHTML status&gt;<br />
SiteID: ${status.properties["optStatus:siteId"]!''}&lt;br /&gt;<br />
User: ${status.properties["optStatus:user"]!''}&lt;br /&gt;<br />
Message: ${status.properties["optStatus:message"]!''}&lt;br /&gt;<br />
Prefix: ${status.properties["optStatus:prefix"]!''}&lt;br /&gt;<br />
Mood: ${status.properties["optStatus:mood"]!''}&lt;br /&gt;<br />
Complete: ${(status.properties["optStatus:complete"]!'false')?string}&lt;br /&gt;<br />
Created: ${status.properties["cm:created"]?string(datetimeformat)}&lt;br /&gt;<br />
Modified: ${status.properties["cm:modified"]?string(datetimeformat)}&lt;br /&gt;<br />
&lt;/#macro&gt;<br />
</code></p>
<p>There&#8217;s one macro for JSON and another for HTML. It still bothers me that the same basic structure is repeated, but at least they are both in the same file and it feels better knowing that this is the one and only place where the data structure for a Status object is defined.</p>
<p>The FreeMarker that returns Status objects as JSON resides in status.get.json.ftl and looks like this:</p>
<p><code>&lt;#import "status.lib.ftl" as statusLib/&gt;<br />
{<br />
"items" : [<br />
&lt;#list results as result&gt;<br />
&lt;@statusLib.statusJSON status=result /&gt;<br />
&lt;#if result_has_next&gt;,&lt;/#if&gt;<br />
&lt;/#list&gt;<br />
]<br />
}</code></p>
<p>You can see the import statement followed by the JSON that sets up the list, and then the call to the FreeMarker macro to format each Status object in the list.</p>
<p>When someone posts a new Status, the new Status object is returned. Rather than repeat the JSON structure for a Status object, the status.post.json.ftl view simply calls the same macro that got called from the GET:</p>
<p><code>&lt;#import "status.lib.ftl" as statusLib/&gt;<br />
{<br />
"status" : &lt;@statusLib.statusJSON status=result /&gt;<br />
}</code></p>
<p>Now if the data structure for a Status object ever needs to change, it only has to be changed in one place.</p>
<p><strong>Don&#8217;t Repeat Yourself</strong></p>
<p>Take a look at your web scripts. Eliminate your duplicate code. And keep a lid on your mocha frappuccino.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2009/06/01/994/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Curl up with a good web script</title>
		<link>http://ecmarchitect.com/archives/2009/04/10/959</link>
		<comments>http://ecmarchitect.com/archives/2009/04/10/959#comments</comments>
		<pubDate>Fri, 10 Apr 2009 15:50:34 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[Content Management]]></category>
		<category><![CDATA[Alfresco Surf]]></category>
		<category><![CDATA[curl]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[testing]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=959</guid>
		<description><![CDATA[Curl is a useful tool for all sorts of things. One specific example of when it comes in handy is when you are developing Alfresco web scripts. On a Surf project, for example, you might divide into a &#8220;Surf tier&#8221; team and a &#8220;Repository tier&#8221; team. Once you&#8217;ve agreed on the interface, including both the [...]]]></description>
			<content:encoded><![CDATA[<p><a title="cURL Home Page" href="http://curl.haxx.se/" target="_blank">Curl</a> is a useful tool for all sorts of things. One specific example of when it comes in handy is when you are developing <a title="Alfresco Home Page" href="http://www.alfresco.com" target="_blank">Alfresco</a> web scripts. On a <a title="About the Alfresco Surf Platform" href="http://wiki.alfresco.com/wiki/Surf_Platform" target="_blank">Surf</a> project, for example, you might divide into a &#8220;Surf tier&#8221; team and a &#8220;Repository tier&#8221; team. Once you&#8217;ve agreed on the interface, including both the URLs and the format of the data that goes back-and-forth between the tiers, the two teams can start cranking out code in parallel.</p>
<p>If you&#8217;re on the repo team, you need a way to test your API, and you probably don&#8217;t have a UI to test it with (that&#8217;s what the other team&#8217;s working on). There are lots of solutions to this but curl is really handy and it runs everywhere (on Windows, use Cygwin).</p>
<p>This post isn&#8217;t intended to be a full reference or how-to for curl, and obviously, you can use curl for a lot of tasks that involve HTTP, not just Alfresco web scripts. Here are some quick examples of using curl with Alfresco web scripts to get you going.</p>
<p><strong>Get a ticket</strong></p>
<p>It&#8217;s highly likely that your web script will require authentication. So the first thing you do is call the login web script to get a ticket.<br />
<code><br />
curl -v "http://localhost:8080/alfresco/service/api/login?u=admin&amp;pw=somepassword"<br />
</code></p>
<p>Alfresco will respond with something like:<br />
<code><br />
&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br />
&lt;ticket&gt;TICKET_e46107058fdd2760441b44481a22e7498e7dbf66&lt;/ticket&gt;<br />
</code></p>
<p>Now you can take that ticket and append it to your subsequent web script calls.</p>
<p>Any web script you&#8217;ve got that accepts GET can be tested using the same simple syntax.</p>
<p><strong>Post JSON to your custom web script</strong></p>
<p>If all you had were GETs you&#8217;d probably just test them in your browser. POSTs, PUTs and DELETEs require a little more doing to test. You&#8217;re going to want to test those web scripts so that when the front-end team has their stuff ready, it all comes together without a lot of fuss.</p>
<p>So let&#8217;s say you&#8217;ve got a web script that the front-end will be POSTing JSON to. To test it out, create a file with some test JSON, then post it to the web script using curl, like this:<br />
<code><br />
curl -v -X POST "http://localhost:8080/alfresco/service/someco/someScript?alf_ticket=TICKET_e46107058fdd2760441b44481a22e7498e7dbf66" -H "Content-Type: application/json" -d @/Users/jpotts/test.json<br />
</code></p>
<p>By the way, did you know that starting with 3.0, if you name your controller with &#8220;.json&#8221; before the &#8220;.js&#8221; the JSON will be sitting in a root variable called &#8220;json&#8221;?  So in this case instead of naming my controller &#8220;someScript.post.js&#8221; I&#8217;d name it &#8220;someScript.post.json.js&#8221; and then in my JavaScript, I can just eval the &#8220;json&#8221; variable that got created for me automatically and start working with the object,  like this:<br />
<code><br />
var postedObject = eval('(' + json + ')');<br />
logger.log("Customer name:" + postedObject.customerName);<br />
</code></p>
<p><strong>Run a CMIS query</strong></p>
<p>With 3.0 Alfresco added an implementation of the proposed CMIS spec to the product. CMIS gives you a Web Services API, a RESTful API, and a SQL-like query language. Once you figure out the syntax, it&#8217;s easy to post CMIS queries to the repository. You can wrap the CMIS query in XML:<br />
<code><br />
&lt;cmis:query xmlns:cmis="http://www.cmis.org/2008/05" &gt;<br />
&lt;cmis:statement&gt;&lt;![CDATA[select * from cm_content where cm_name like '%Foo%']]&gt;&lt;/cmis:statement&gt;<br />
&lt;/cmis:query&gt;<br />
</code></p>
<p>Then post it using the same syntax as you saw previously, but with a different Content-Type in the header, like this:<br />
<code><br />
curl -v -X POST "http://localhost:8080/alfresco/service/api/query?alf_ticket=TICKET_e46107058fdd2760441b44481a22e7498e7dbf66" -H "Content-Type: application/cmisquery+xml" -d @/Users/jpotts/cmis-query.xml<br />
</code></p>
<p>Alfresco will respond with ATOM, but it&#8217;s a little verbose so I won&#8217;t take up space here to show you the result. Also, I noticed this bombed when I ran it against 3.1 Enterprise but I haven&#8217;t drilled down on why yet.</p>
<p><strong>Create a new object using CMIS ATOM</strong></p>
<p>Issuing a GET against a CMIS URL returns ATOM. But CMIS URLs can also accept POSTed ATOM to do things like create new objects. For example, to create a new content object you would first create the ATOM XML:<br />
<code><br />
&lt;?xml version="1.0" encoding="utf-8"?&gt;<br />
&lt;entry xmlns="http://www.w3.org/2005/Atom" xmlns:cmis="http://www.cmis.org/2008/05"&gt;<br />
&lt;title&gt;Test Plain Text Content&lt;/title&gt;<br />
&lt;summary&gt;Plain text content created via CMIS POST&lt;/summary&gt;<br />
&lt;content type="text/plain"&gt;SGVyZSBpcyBzb21lIHBsYWluIHRleHQgY29udGVudC4K&lt;/content&gt;<br />
&lt;cmis:object&gt;<br />
&lt;cmis:properties&gt;<br />
&lt;cmis:propertyString cmis:name="ObjectTypeId"&gt;&lt;cmis:value&gt;document&lt;/cmis:value&gt;&lt;/cmis:propertyString&gt;<br />
&lt;/cmis:properties&gt;<br />
&lt;/cmis:object&gt;<br />
&lt;/entry&gt;<br />
</code></p>
<p>Note that the content has to be Base64 encoded. In this case, the content is plain text that reads, &#8220;Here is some plain text content.&#8221; One way to encode it is to use OpenSSL like &#8220;openssl base64 -in &lt;infile&gt; -out &lt;outfile&gt;&#8221;. The exact syntax of ATOM XML with CMIS is the subject for another post.</p>
<p>Once you&#8217;ve got the XML ready to go, post it using the same syntax shown previously, with a different Content-Type in the header:<br />
<code><br />
curl -v -X POST "http://localhost:8080/alfresco/service/api/node/workspace/SpacesStore/18fd9821-42a5-4c6a-86d3-3f252679cf7d/children?alf_ticket=TICKET_e46107058fdd2760441b44481a22e7498e7dbf66" -H "Content-Type: application/atom+xml" -d @/Users/jpotts/testCreate.atom.xml<br />
</code></p>
<p>The node reference in the URL above is a reference to the folder in which this new child will be created. There&#8217;s also a similar URL that uses the path instead of a node ref if that&#8217;s more your thing.</p>
<p><strong>Refreshing Web Scripts from Ant</strong></p>
<p>One of the things you do quite frequently when you develop web scripts is tell Alfresco to refresh its list of web scripts. There are lots of ways to automate this, but one is to create an Ant task that uses curl to invoke the web script refresh URL. This lets you deploy your changes and tell Alfresco to refresh the list in one step (and makes sure you and your teammates never forget to do the refresh).<br />
<code><br />
&lt;target name="deploy-webscripts" depends="deploy" description="Refreshes the list of webscripts"&gt;<br />
&lt;exec executable="curl"&gt;<br />
&lt;arg value="-d"/&gt;<br />
&lt;arg value="reset=on"/&gt;<br />
&lt;arg value="http://${alfresco.web.url}/service/index"/&gt;<br />
&lt;/exec&gt;<br />
&lt;/target&gt;<br />
</code></p>
<p>In this example, the &#8220;deploy&#8221; ant task this task depends on is responsible for copying the web scripts to the appropriate place in the exploded Alfresco WAR. (Thanks to my colleague Eric Shea (http://www.eshea.net/2009/01/30/alfresco-dev-survivors-kit-part-1/) for this tip).</p>
<p>So there you go. It&#8217;s not Earth-shattering but it might give you a productivity boost if you don&#8217;t already have curl or an alternative already in your bag of tricks.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2009/04/10/959/feed</wfw:commentRss>
		<slash:comments>32</slash:comments>
		</item>
		<item>
		<title>Grasping Thumbnails in Alfresco 3</title>
		<link>http://ecmarchitect.com/archives/2009/03/03/913</link>
		<comments>http://ecmarchitect.com/archives/2009/03/03/913#comments</comments>
		<pubDate>Wed, 04 Mar 2009 03:44:09 +0000</pubDate>
		<dc:creator>jpotts</dc:creator>
				<category><![CDATA[Alfresco]]></category>
		<category><![CDATA[Alfresco Developer Series]]></category>
		<category><![CDATA[General]]></category>
		<category><![CDATA[thumbnail]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://ecmarchitect.com/?p=913</guid>
		<description><![CDATA[With Alfresco 3 (both Labs and Enterprise), Alfresco added a new thumbnail service. It isn&#8217;t documented too well yet so I thought I&#8217;d write up a quick example. What is it The Thumbnail Service is used to create alternate renditions of objects. Typically, those alternate renditions are small images called &#8220;thumbnails&#8221;. You can see a [...]]]></description>
			<content:encoded><![CDATA[<p>With Alfresco 3 (both Labs and Enterprise), <a title="Alfresco Home Page" href="http://www.alfresco.com" target="_blank">Alfresco</a> added a new thumbnail service. It isn&#8217;t documented too well yet so I thought I&#8217;d write up a quick example.</p>
<p><strong>What is it</strong></p>
<p>The Thumbnail Service is used to create alternate renditions of objects. Typically, those alternate renditions are small images called &#8220;thumbnails&#8221;. You can see a working application of the thumbnail service if you take a look at Alfresco Share&#8217;s document library. When you upload a document, the thumbnail service is invoked, and a small image is shown next to each item in the list.</p>
<p><strong>Where thumbnails live</strong></p>
<p>Like everything else in Alfresco, thumbnails are stored as nodes. Nodes are instances of cm:thumbnail and are stored as children of the object they represent. (You can see this for yourself by looking at the thumbnailed object in the node browser). Objects can have any number of thumbnails. This lets you have thumbnails of different sizes and mime types, for example.</p>
<p>Once Alfresco generates a thumbnail for an object, the object will have the cm:thumbnailed aspect applied to it so it is easy to find or filter objects based on whether or not they have at least one thumbnail.</p>
<p><strong>Thumbnail definitions &amp; thumbnail names</strong></p>
<p>Every thumbnail has a thumbnail definition. The thumbnail definition keeps track of things like the mime type, transformation options, placeholder path, and thumbnail name. The thumbnail name uniquely identifies the thumbnail definition in the thumbnail registry. When you want to generate or display a thumbnail for an object, you must specify the name. For example, given a thumbnail definition named &#8220;scImageThumbnail&#8221;, you could use JavaScript to create a thumbnail for an object by calling the &#8220;createThumbnail&#8221; method on a ScriptNode like this:</p>
<p><code>document.createThumbnail("scImageThumbnail", true);</code></p>
<p>The first argument is the name of the thumbnail definition. The second argument says the thumbnail should be generated asynchronously.</p>
<p><strong>Registering thumbnail definitions</strong></p>
<p>The thumbnail registry needs to know about your thumbnail definitions. The out-of-the-box thumbnails are registered in the thumbnail-service-context.xml file. I don&#8217;t see a clean way to extend that without repeating the definitions, so in my example, I wrote a bean that calls the Thumbnail Registry and registers the custom thumbnail definitions provided in the Spring context file:</p>
<pre><code>
public class ThumbnailRegistryBootstrap {
  private ThumbnailService thumbnailService;
  private List&lt;ThumbnailDefinition&gt; thumbnailDefinitions;
  private Logger logger = Logger.getLogger(ThumbnailRegistryBootstrap.class);

public void init() {
  ThumbnailRegistry thumbnailRegistry = thumbnailService.getThumbnailRegistry();
    for (ThumbnailDefinition thumbDef : thumbnailDefinitions) {
      logger.info("Adding thumbnail definition:" + thumbDef.getName());
      thumbnailRegistry.addThumbnailDefinition(thumbDef);
    }
}

public void setThumbnailService(ThumbnailService thumbnailService) {
  this.thumbnailService = thumbnailService;
}

public void setThumbnailDefinitions(
  List&lt;ThumbnailDefinition&gt; thumbnailDefinitions) {
  this.thumbnailDefinitions = thumbnailDefinitions;
}

}
</code></pre>
<p>So this class will add all of my thumbnail definitions to the thumbnail registry. The class and the definitions are configured in a Spring context file. The config for a single thumbnail called &#8220;scImageThumbnail&#8221; which is a PNG 100 pixels high and retains the original aspect ratio of the image would be:</p>
<pre><code>
&lt;bean id="someco.thumbnailRegistry"
 class="com.someco.thumbnails.ThumbnailRegistryBootstrap"
 depends-on="ThumbnailService"
 init-method="init"&gt;
  &lt;property name="thumbnailService" ref="ThumbnailService" /&gt;
  &lt;property name="thumbnailDefinitions"&gt;
    &lt;list&gt;
      &lt;bean class="org.alfresco.repo.thumbnail.ThumbnailDefinition"&gt;
        &lt;property name="name" value="scImageThumbnail" /&gt;
        &lt;property name="mimetype" value="image/png"/&gt;
        &lt;property name="transformationOptions"&gt;
          &lt;bean  class="org.alfresco.repo.content.transform.magick.ImageTransformationOptions"&gt;
            &lt;property name="resizeOptions"&gt;
              &lt;bean class="org.alfresco.repo.content.transform.magick.ImageResizeOptions"&gt;
              &lt;property name="height" value="100"/&gt;
              &lt;property name="maintainAspectRatio" value="true"/&gt;
              &lt;property name="resizeToThumbnail" value="true" /&gt;
            &lt;/bean&gt;
          &lt;/property&gt;
        &lt;/bean&gt;
     &lt;/property&gt;
     &lt;property name="placeHolderResourcePath" value="alfresco/extension/thumbnail/thumbnail_placeholder_scImageThumbnail.png" /&gt;
   &lt;/bean&gt;
 &lt;/list&gt;
 &lt;/property&gt;
&lt;/bean&gt;
</code></pre>
<p>The placeholder is a graphic that the thumbnail service can return as the thumbnail if the thumbnail for a given node has not been generated. In my example I just copied one of the out-of-the-box placeholders and renamed it but you could use anything you want there.</p>
<p><strong>Example</strong></p>
<p>I built a simple example to show how this works. Here is a screencast that shows it running or you can <a href="/images/someco-thumbnail-example-project.zip">download the source</a> and build it yourself.</p>
<p><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="320" height="265" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="src" value="http://www.youtube.com/v/lENsFH8b6U0&amp;hl=en&amp;fs=1&amp;rel=0" /><embed type="application/x-shockwave-flash" width="320" height="265" src="http://www.youtube.com/v/lENsFH8b6U0&amp;hl=en&amp;fs=1&amp;rel=0" allowscriptaccess="always" allowfullscreen="true"></embed></object></p>
<p>In the example, a simple form is presented to allow a file to be uploaded. The form posts to a web script which creates a new node using the file provided. The form GET and POST web scripts are essentially the &#8220;helloworldform&#8221; web scripts from the Alfresco Developer Guide.</p>
<p>The &#8220;image list&#8221; is a simple GET web script that queries the folder where the images are uploaded to and writes out a list of image tags. The interesting thing to note here is the URL that&#8217;s used:</p>
<p><code>${url.serviceContext}/api/node/workspace/SpacesStore/${image.id}/content/thumbnails/scImageThumbnail?ph=true&amp;c=queue</code></p>
<p>That URL is an out-of-the-box web script that returns the specified thumbnail for a given node reference. In my example I&#8217;m using the &#8220;ph&#8221; and &#8220;c&#8221; arguments. The &#8220;ph&#8221; argument says whether or not the placeholder image should be returned if the thumbnail does not exist. The &#8220;c&#8221; argument says that if a thumbnail doesn&#8217;t exist, queue a request for thumbnail creation. (Note that the descriptor says the queue create argument is &#8220;qc&#8221; but if you look at the controller source you&#8217;ll see it is actually just &#8220;c&#8221;. I&#8217;ll check to see if there&#8217;s a Jira on that).</p>
<p>When you add a new image and then go to the image list you&#8217;ll see the placeholder graphic. Behind the scenes, a thumbnail creation request has been queued. If you refresh the page, the thumbnail should show up because Alfresco has had a chance to generate it. If you wanted to queue the request when the node is created, you could either create a rule on the folder that holds the images, or you could add a call to &#8220;createThumbnail&#8221; in the upload POST web script controller, as shown earlier. (I&#8217;ve got an example of that commented out in the source).</p>
<p><strong>That&#8217;s it</strong></p>
<p>Hopefully this has given you some insight into the new thumbnail service in Alfresco. If you want to play with it yourself, you can download the source for the example and build it with Ant (make sure you set build.properties to match your environment first) by running &#8220;ant deploy&#8221;. Make sure you&#8217;ve got ImageMagick installed on your Alfresco server&#8211;the thumbnail service depends on it. You&#8217;ll also need the SDK to compile the registry bootstrap class. If you want to see what the thumbnail service is actually doing you&#8217;ll need the Alfresco source. None of the thumbnail source is included in the source code that currently accompanies the SDK.</p>
]]></content:encoded>
			<wfw:commentRss>http://ecmarchitect.com/archives/2009/03/03/913/feed</wfw:commentRss>
		<slash:comments>35</slash:comments>
		</item>
	</channel>
</rss>

