I exist as I amDo not go where the path may lead, go instead where there is no path and leave a trail. --Ralph Waldo EmersonJekyll2023-07-09T22:04:06-06:00https://jared.ottleys.net/Jared Ottleyhttps://jared.ottleys.net/jared@ottleys.nethttps://jared.ottleys.net/alfresco/alfresco-cmis-browser-binding-delete2014-04-14T00:00:00-06:002014-04-14T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>I’m in the midst of rewriting a CMIS client. We are moving from CMIS 1.0 atompub bindings to CMIS 1.1 browser bindings. I’ve worked out how to do a DELETE and wanted to document an example that others could extrapolate from.
<br /><br />
A browser binding DELETE should follow the model of a form POST according to the documentation. Playing around with the provided examples (there were no DELETE examples that I found doing a quick google search) I found it to be a simpler than a form POST. I should also note that these calls are governed by the <a href="http://en.wikipedia.org/wiki/Same-origin_policy" title="Same Origin Policy">same origin policy</a> so you’ll need to use either <a href="http://en.wikipedia.org/wiki/JSONP" title="JSONP">JSON-P</a> or a <a href="http://en.wikipedia.org/wiki/Cross-origin_resource_sharing" title="Cross-origin resource sharing">CORS</a> enabled <a href="http://www.slideshare.net/jottley/cors-enable-alfresco-for-cors" title="CORS enable Alfresco">server</a> to enable building applications against the CMIS enabled repository.
<br /><br />
<em>I’m using curl for my examples.</em>
<br /><br />
There are two ways to address the object that you want to delete: using the objectId or by path.</p>
<h3 id="using-objectid">Using objectId</h3>
<p>To delete by objectId we need to include the ‘objectId=’ parameter along with cmis objectId (ex. 7ab93c6a-9520-40dd-be74-bae52f4bd6ca;1.0).</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">&objectId<span class="o">=</span>7ab93c6a-9520-40dd-be74-bae52f4bd6ca<span class="p">;</span>1.0</code></pre></figure>
<h3 id="by-path">By Path</h3>
<p>To delete by path you need to include the full path in the URL. There is no need to include the objectId in the by path call as the path has precedence over the objectId</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">Sites/internal2/documentLibrary/test.txt</code></pre></figure>
<p>Now with the basics of addressing the document or folder under our belt we can pull together the rest of the call.
<br /><br />
With the atompub binding we made a delete using the HTTP DELETE method. With the browser binding a delete is made using the HTTP POST method. To indicate our call is a delete we add the ‘cmisaction=’ parameter with a value of delete to our call.</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">cmisaction</span><span class="o">=</span>delete</code></pre></figure>
<p>A full objectId call (made against Alfresco Cloud) would use a URL like the following:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">https://api.alfresco.com/ottleys.net/public/cmis/versions/1.1/browser/root?cmisaction<span class="o">=</span>delete&objectId<span class="o">=</span>804f6300-38cd-48a4-86db-499f58497ae5<span class="p">;</span>1.0</code></pre></figure>
<p>The same call using a path:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">https://api.alfresco.com/ottleys.net/public/cmis/versions/1.1/browser/root/Sites/internal2/documentLibrary/test.txt?cmisaction<span class="o">=</span>delete</code></pre></figure>
<p>Using the objectId example from above a call using curl against the Alfresco Cloud Public API would look like:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">curl <span class="nt">-X</span> POST <span class="s2">"https://api.alfresco.com/ottleys.net/public/cmis/versions/1.1/browser/root?cmisaction=delete&objectId=804f6300-38cd-48a4-86db-499f58497ae5;1.0"</span> <span class="nt">--header</span> <span class="s2">"Authorization:Bearer 42b8c319-344c-4273-b94c-b4c23482be6b"</span> <span class="nt">-IL</span></code></pre></figure>
<p>(For those unfamiliar with curl I’ve added the -IL options to the command so that I can see details about the response (not including the body).</p>
<h3 id="what-about-response-codes">What about response codes?</h3>
<p>With atompub a successful delete would have resulted in a 204 response code and no response body. With the browser bindings a 200 response code is returned still with no body. (My preference would be that it returned a 204. This would keep consistency across the bindings and would match what I believe is proper DELETE response.)
<br /><br />
And response codes for Errors? You still see a 401 when a user needs to authenticate and a 404 if the object can’t be found. All other errors that I saw came back as 409s. This includes locked documents and attempting to delete folders containing children. If you receive a 409 result there are two json values you can look at to find the reason for the error: exception (the short description) and message (a fuller description of the error).</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-cmis-browser-binding-delete">Alfresco: CMIS Browser Binding DELETE example</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on April 14, 2014.</p>https://jared.ottleys.net/alfresco/alfresco-property-decorators-cont2012-04-02T00:00:00-06:002012-04-02T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>In my post on <a href="http://jared.ottleys.net/alfresco/alfresco-property-decorators" title="Alfresco: Property Decorators">Property Decorators</a> I pointed out that there was an <a href="https://issues.alfresco.com/jira/browse/ALF-13038">issue</a> adding a customer property decorator: you couldn’t create a custom bean to perform the mapping and had to overwrite the out of the box bean. Well <a href="http://twitter.com/#!/mikehatfield">Mike Hatfield</a> has found a solution which is just awesome: <a href="http://forum.springsource.org/showthread.php?53358-Simple-merge-of-util-map">Map Merge</a>.
<br /><br />
It is possible to merge maps of a child bean to a parent bean. Here is how it is done: Create a new bean, where the parent is the <code class="language-plaintext highlighter-rouge">applicationScriptUtils</code> bean. Add a single property of <code class="language-plaintext highlighter-rouge">decoratedProperties</code> and define a map in the property. When you define the map add the attribute <code class="language-plaintext highlighter-rouge">merge="true"</code>. Finally, add your custom property to decorator bean mappings. Done!</p>
<p>Here is an example:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"customApplicationScriptUtils"</span> <span class="na">parent=</span><span class="s">"applicationScriptUtils"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"decoratedProperties"</span><span class="nt">></span>
<span class="nt"><map</span> <span class="na">merge=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><entry</span> <span class="na">key=</span><span class="s">"alf:propertyHolder"</span><span class="nt">></span>
<span class="nt"><ref</span> <span class="na">bean=</span><span class="s">"customDecoratorBean"</span><span class="nt">/></span>
<span class="nt"></entry></span>
<span class="nt"></map></span>
<span class="nt"></property></span>
<span class="nt"></bean></span></code></pre></figure>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-property-decorators-cont">Alfresco: Property Decorators — cont.</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on April 02, 2012.</p>https://jared.ottleys.net/alfresco/alfresco-simple-workflow-web-scripts2012-04-01T00:00:00-06:002012-04-01T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>Continuing on from my previous post on <a href="http://jared.ottleys.net/alfresco/alfresco-simple-workflow" title="Alfresco: Simple Workflow">Simple Workflows</a> let’s look at using the Simple Workflow model in a few web scripts.</p>
<p>The first web script will add a simple workflow to a node. The second will allow us to signal the transition of accept or reject on that node.</p>
<p><strong>Add a Workflow</strong></p>
<p>As discussed in the previous post, a simple workflow is made up of two paths: accept or reject with a copy or move to another folder. You are also able to set the name presented to the user for the accept or reject step. This is very useful as what we may be presenting to the user is to not accept or reject documents, but rather sending the document to two different group owned folders based on context/content of those documents. At least one step is required. Alfresco defaults “accept” for the required step and makes “reject” the optional step. These need to be passed to our web script. The payload is passed as json. A full example of this looks as follows:</p>
<pre class="brush: plain; title: ; notranslate" title="">{
"simpleworkflow": {
"accept": {
"name": "acceptstep",
"move": true,
"folder": "workspace://SpacesStore/ebd7ff99-b423-4e45-ad4f-71929ce4c089"
},
"reject": {
"name": "rejectstep",
"move": true,
"folder": "workspace://SpacesStore/0239b2ed-ebfa-4b9d-a7fa-f0404448cffc"
}
}
}
</pre>
<p>In the above example, the reject object is optional.</p>
<p>Both the accept and reject objects are made up of the three required values of the simple workflow aspect: name, move and folder.</p>
<p>The name field maps to either approveStep or rejectStep. It is the name presented in the Alfresco UIs. But it could also be used in a custom interface as they are returned as part of the list of properties for the node.</p>
<p>The move field maps to either approveMove or rejectMove. This boolean field indicates with a true value to move the node or, if false, to copy the node.</p>
<p>The final field, folder, maps to approveFolder or rejectFolder. This is the folder node where the document should be moved or copied to when the appropriate action is taken.</p>
<p>The web script can be call by passing the simpleworkflow json object to <code class="language-plaintext highlighter-rouge">simpleworkflow/add/{store_type}/{store_id}/{id}</code>. Upon completion a json object of <code class="language-plaintext highlighter-rouge">{ "success": true|false] }</code>. True upon successful completion, false if there was an error adding the simpleworkflow aspect to the node.</p>
<p><strong>Using a Simple Workflow from a Web Script</strong></p>
<p>This web script was originally developed for a customer to return XML, but I’ve now updated it to return json by default. The XML is maintained here for backwards compatibility.</p>
<p>When a simple workflow aspect is added to a node, it just adds the properties of what should occur when an accept or reject action is taken. The actual actions of what happens is built into UIs. So the web scripts need to do the same: perform the copy or move to the targeted folder. The call is pretty simple: <code class="language-plaintext highlighter-rouge">simpleworkflow/{accept|reject}/{store_type}/{store_id}/{id}</code>. The json or XML that is returned let us know of either the success or the failure of this action. Failures can provide some information on why the failure occurred. A success messages, provides more detail: what action occured — copy or move, which step was taken — returning the name as set in the approveStep or rejectStep properties, the folder that document was moved or copied to. The destination that is returned is the human readable path. (If there is any interest we can update this to return the nodeRef as well as the path.)</p>
<p>The json returned looks like:</p>
<pre class="brush: plain; title: ; notranslate" title="">{
"success": true,
"action": "move",
"step": "acceptStep",
"destination": "/Company Home/Sites/workflow/docmentLibrary/workflow/accept"
}
</pre>
<p>These web scripts can be <a href="http://code.google.com/p/alfresco-simple-workflow-webscripts/">downloaded</a> from its Google Code Project.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-simple-workflow-web-scripts">Alfresco: Simple Workflow Web Scripts</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on April 01, 2012.</p>https://jared.ottleys.net/alfresco/alfresco-simple-workflow2012-03-31T00:00:00-06:002012-03-31T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>When I worked on the sales side at Alfresco, one of the easiest things to demo was a simple workflow. It was usually something that enabled the prospect to tick off a major requirement on their long list of things that the solution they were looking for needed. Alfresco has two kinds of workflow: simple and for lack of a better word advanced. Advanced workflows usually require some level of business logic, multiple steps, several actors, etc. Advanced workflows are implemented using <a href="http://www.activiti.org/">Activiti</a> (Alfresco 4.0 forward) or JBPM (pre Alfresco 4.0 and maintained for legacy in Alfresco 4.0) Simple workflows provide an accept, a reject, and then the content is either copied or moved to another folder.</p>
<p>What I really like about simple workflows is how they are implemented and how you can model your own customizations or applications using this pattern. As a follow up to this post I’m going to share a web script to add a simple workflow to a node and then another to progress the workflow by accepting or rejecting it.</p>
<p><strong>Let’s dig in!</strong></p>
<p>Simple workflows present, as stated above, the ability to accept and reject the document and then copy or move that document to another space. The key to a simple workflow is the model.</p>
<pre class="brush: xml; title: ; notranslate" title=""><aspect name="app:simpleworkflow">
<parent>app:workflow</parent>
<properties>
<property name="app:approveStep">
<type>d:text</type>
<protected>true</protected>
</property>
<property name="app:approveFolder">
<type>d:noderef</type>
<protected>true</protected>
</property>
<property name="app:approveMove">
<type>d:boolean</type>
</property>
<property name="app:rejectStep">
<type>d:text</type>
<protected>true</protected>
</property>
<property name="app:rejectFolder">
<type>d:noderef</type>
<protected>true</protected>
</property>
<property name="app:rejectMove">
<type>d:boolean</type>
</property>
</properties>
</aspect>
</pre>
<p>Each node that has a simple workflow attached to it has this aspect applied to it, which specifies through properties what steps are available, what that step should be called (acceptStep and rejectStep) and then what should occur when that step is taken: should it be moved or copied (acceptMove and rejectMove)? and where it should go (acceptFolder and rejectFolder).</p>
<p>Knowing this opens many doors for us. Because the steps are properties on the node, we can modify them, if needs be, using either javascript or Java. We can also build on top of this. When I worked on the Consulting team at Alfresco a customer wanted to have a three step workflow: accept, reject and other. Modeling this was simple. All we really needed to do was add three simple properties: the step name (text), the destination node (noderef) and if it is a move or copy (boolean). A bit more complex was adding the logic for exposing this through the Explorer UI. (This customer had not yet moved to Share). Because we did not want to affect all of the simple workflows already in place, we opted to add an additional 3 step simple workflow, giving them the ability to have up to 5 different paths that could be taken for a single node.</p>
<p>We can also tie simple workflows into more complex actions: dynamically adding simple workflows or scripting more complex actions that allow users to manually take the workflow steps but also allowing programatic lifecycle or state to take simple workflow actions. By jumping out of Explorer or Share and into a custom application you can expose simple workflows from the repository through a web script (an easier task than modifying JSF) and one that I’ve used for several different Alfresco implementations. We will cover that in my next post.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-simple-workflow">Alfresco: Simple Workflow</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on March 31, 2012.</p>https://jared.ottleys.net/alfresco/alfresco-scheduled-jobs2012-02-21T00:00:00-07:002012-02-21T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><strong>Note:</strong> <em>@tommoz has pointed out to me a blog post that I’d never seen before from @Ixxus which is a <a href="http://t.co/RAOFy23l">complete example of writing a scheduled job</a> (spring beans and all). You should really check out their excellent example if you need a full example of writing scheduled jobs for Alfresco.</em></p>
<p>One of the things that is easy to forget when adding/writing a new scheduled jobs is to wrap your code in a RetryingTransactionHelper _and_ a RunAs. When executing the scheduled job we need to make sure that we provide a transaction in which to perform your custom code and a user to perform the code under.</p>
<p>Example:</p>
<pre class="brush: java; title: ; notranslate" title="">public void execute()
{
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
{
@Override
public Object doWork()
throws Exception
{
RetryingTransactionCallback<;Object>; txnWork = new RetryingTransactionCallback<Object>()
{
public Object execute()
throws Exception
{
//Add logic here
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(txnWork, false);
return null;
}
}, user_authority);
}
</pre>
<p>Hopefully this template will prove usefully to anyone writing a custom scheduled actions.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-scheduled-jobs">Alfresco: Scheduled Jobs</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on February 21, 2012.</p>https://jared.ottleys.net/alfresco/alfresco-property-decorators2012-02-15T00:00:00-07:002012-02-15T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><strong>Note</strong>: <em>After fighting through some upgrade issues with MySQL…and it still not being 100% fixed…I’m finally at a point where I can comfortably write to the DB and bring to you two, thats right two!, new posts in quick succession. I’m hoping to make this a trend to have two new posts a month (so look for 4 this month as these two should have gone live last month). So without further ado….</em></p>
<p>One of the exciting, at least in my opinion, new features of Alfresco 4 is the <a href="http://blogs.alfresco.com/wp/mikeh/2011/09/26/share-document-library-extensions-in-v4-0/">Share Document Library Extension’s</a> (hat tip to <a href="https://twitter.com/#!/mikehatfield">Mike Hatfield)</a>. In the above post Mike goes in to detail on how to use this new feature and I’m going to share an example on how I’m using it as part of integration with Dropbox I’m working on.</p>
<h3 id="the-problem-set"><strong>The Problem Set</strong></h3>
<p>As part of the integration we need to add a status indicator (see the <a href="http://blogs.alfresco.com/wp/mikeh/2011/09/26/share-document-library-extensions-in-v4-0/">above article </a>for in depth details on status indicators) to, as the name states, indicate that the file/folder is in a specific state, ie synced to Dropbox. Early on this was a simple tasks: does it have a specific aspect or not. As the project has moved forward, we’ve raised the complexity: does it have a specific association and does that association have a child association of a specific value…a direct correlation between this child association and a specific user. Now that we have moved beyond what the out of the box evaluators can do directly, what are our options?</p>
<h3 id="options">Options?</h3>
<p>The Share Document Library Extension Framework provides two options for retrieving additional metadata/values from the repository tier: Custom Responses and Property Decorators.</p>
<p>Custom Responses allow you to return information that is not specific to any node. Alfresco 4 uses this to return information about our Sharepoint Protocol integration.</p>
<p>Property Decorators allow you to return node specific information in a more usable format to the web tier (ie Share). For example rewriting a nodeRef to a filename, or a username as first and last name. Or as in our case, a new set of properties or key/value pairs.</p>
<p>Since we are looking for specific information about a node, a property decorator is what we will need.</p>
<h3 id="implemention">Implemention</h3>
<p>A property decorator needs to have the following:</p>
<p>A content model property to hold a map of properties. In our model we added a property similar to:</p>
<pre class="brush: xml; title: ; notranslate" title=""><property name="alf:propertyHolder">
<type>d:boolean</type>
<default>false</default>
<index enabled="false"/>
</property>
</pre>
<p>This property will never be needed in the index since it will always be the same value until it is requested in Share.</p>
<p>Next we need to implement our logic. This requires a new Java class that implements PropertyDecorator.</p>
<pre class="brush: java; title: ; notranslate" title="">public class CustomProperty
implements PropertyDecorator
{
//Add any needed services
@Override
public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value)
{
Map<String, Serializable> map = new LinkedHashMap<String, Serializable>(4);
//Add logic here
//One to many
map.put("key", value);
return (Serializable)map;
}
}
</pre>
<p>This also requires a new Spring bean definition.</p>
<pre class="brush: xml; title: ; notranslate" title=""><bean id="customProperty" class="org.alfresco.extension.repo.jscript.app.CustomProperty">
<!-- Any needed service -->
</bean>
</pre>
<p>Lastly we need to add it to the list of propertyDecorators. <em><strong>Update:</strong> I’ve add a <a href="http://jared.ottleys.net/alfresco/alfresco-property-decorators-cont" title="Alfresco: Property Decorators — cont.">new post</a> that covers how the mapping should occur and added a new bean here.</em> <del datetime="2012-04-02T20:26:51+00:00">Currently this requires overwriting the out of the box definition for applicationScriptUtils. This could cause issues if you have several different AMPs that need to overwrite this bean. (I’ve opened ALF-13038 for this issue).<br /> </del></p>
<pre class="brush: xml; title: ; notranslate" title=""><bean id="customApplicationScriptUtils" parent="applicationScriptUtils">
<property name="decoratedProperties">
<map merge="true">
<entry key="alf:propertyHolder">
<ref bean="customDecoratorBean"/>
</entry>
</map>
</property>
</bean>
</pre>
<p>Now this completes the repo side of our extension. Now we need to work with the values returned on the Share side. We needr a status indicator so we’ll focus there.</p>
<p>First we’ll define the evaluator. We need to add the evaluator to <code class="language-plaintext highlighter-rouge">web-extension/custom-slingshot-something-context.xml</code></p>
<pre class="brush: xml; title: ; notranslate" title=""><bean id="evaluator.doclib.indicator.custom" parent="evaluator.doclib.metadata.value">
<property name="accessor" value="properties.customProperty"/>
<property name="comparator">
<bean class="org.alfresco.web.evaluator.StringEqualsComparator">
<property name="value" value="true" />
</bean>
</property>
</bean>
</pre>
<p>It is important to note that the value of the accessor is not the content model property name, but the key value used in our map from the CustomProperty class.</p>
<p>And finally, we use the evaluator in our indicator config. This is added to our <code class="language-plaintext highlighter-rouge">share-config-custom.xml</code> under the <code class="language-plaintext highlighter-rouge">META-INF</code> in our custom jar file.</p>
<pre class="brush: xml; title: ; notranslate" title=""><config evaluator="string-compare" condition="DocumentLibrary">
<indicators>
<indicator id="customDecorator" index="250" icon="custom-16.png">
<evaluator>evaluator.doclib.indicator.custom</evaluator>
</indicator>
</indicators>
</config>
</pre>
<p>In conclusion, the Share Document Library Extension Framework is a powerful new tool in the developer toolbox.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-property-decorators">Alfresco: Property Decorators</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on February 15, 2012.</p>https://jared.ottleys.net/alfresco/alfresco-share-discussion-notification2011-06-02T00:00:00-06:002011-06-02T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>A few months ago in the Alfresco Forums, a user asked about adding email notification to Share Site Discussions. We went back and forth working out a rough way to accomplish this (some of this in private messages). And then a couple of weeks ago the question came up internally. I passed on what we had figured out and told them I would put together a blog post.</p>
<h2 id="so-what-are-we-trying-to-accomplish"><strong>So what are we trying to accomplish?</strong></h2>
<p>The Share Site Discussion Components supports the following:</p>
<div>
<ul>
<li>
Threaded Discussions – Post topic and replies
</li>
<li>
Dynamic Filtering – Tags/New/Hot/All/My Topics
</li>
<li>
RSS Feed for latest discussions
</li>
</ul>
</div>
<p>Notifications of new posts can happen over RSS and to the Site Activities Dashlet both of which require access to the system, most likely behind a firewall. Email notifications can provide updates the don’t require direct access to the system, unless you are looking to update the post.</p>
<h2 id="where-to-start"><strong>Where to start?</strong></h2>
<p>There are a few things that we need to know as we get started. First, when a site is created in Alfresco Share, the content nodes supporting discussions are not created. Only after a user accesses the discussion component does it get created.</p>
<p>Second, discussions are created as <code class="language-plaintext highlighter-rouge">fm:topic</code> types. Posts are added as children of <code class="language-plaintext highlighter-rouge">fm:topic</code> as a <code class="language-plaintext highlighter-rouge">fm:post</code>.</p>
<h2 id="using-a-rule"><strong>Using a Rule</strong></h2>
<p>The simplest way to add notifications is as a rule via the Alfresco Explorer client on the discussion space that is created for the site, under the Sites space. Adding a rule to the disucssions space matching all content items will result on two emails (one for the creation of the topic and one for the first post). So we need to be more specific: We need to expose the Forum Post and Topic types as Subtypes to the rules engine. You add fm:topic as well if you want to enable delete notifications</p>
<p>To <code class="language-plaintext highlighter-rouge">web-client-config-custom.xml</code> add</p>
<pre class="brush: xml; title: ; notranslate" title=""><alfresco-config>
<config evaluator="string-compare" condition="Action Wizards">
<subtypes>
<type name="fm:post"/>
<type name="fm:topic"/>
</subtypes>
</config>
</alfresco-config>
</pre>
<p>Topics are the best choice for building rules on. Notifications on <code class="language-plaintext highlighter-rouge">fm:topic</code> will work for create and delete but not update. The title of a topic is stored on the first post of a discussion. You can’t delete individual posts but you can delete a Topic so when specifying a delete notification it can be made directly against a topic,but again…it is easier to just do the same using fm:post.</p>
<p>The notification template would be added to email templates and would look like:</p>
<pre class="brush: plain; title: ; notranslate" title="">A new post is available in the '${document.parent.childAssocs["cm:contains"][0].properties.title}' dicussion in the '${document.parent.parent.parent.properties.name}' site, it was added by ${person.properties.firstName}<#if person.properties.lastName?exists> ${person.properties.lastName}</#if>.
You can view it through this link:
${url.serverPath}/share/page/site/${document.parent.parent.parent.properties.name}/discussions-topicview?topicId=${document.parent.children[0].name}&listViewLinkBack=true
</pre>
<h2 id="email-notification-rule"><strong>Email Notification Rule</strong></h2>
<p>An email rule is simple to setup but the maintenance cost can be high. You have to add each user/group manually and Share site groups are not visible to the Explorer client.</p>
<p>The next option is to script the notification. A Share site group or dynamically build email list of those participating in the discussion. (A nice to have would be to build in a watch option for those who may not participate in the discussion but want to be notified.)</p>
<h2 id="mail-action-sample-for-group-or-individual"><strong>Mail action sample for group or individual.</strong></h2>
<p>The <code class="language-plaintext highlighter-rouge">to_many</code> paramater currently only supports one group at a time….if you need to send to multiple people or groups, you’ll need to loop over the <code class="language-plaintext highlighter-rouge">to_many</code> or <code class="language-plaintext highlighter-rouge">to</code> paramaters and execute against each.</p>
<pre class="brush: jscript; title: ; notranslate" title="">var siteGroup = "GROUP_site_" + document.parent.parent.parent.name;
var template = "Data Dictionary/Email Templates/Notify Email Templates/share_discussion_notification.ftl";
// create mail action
var mail = actions.create("mail");
mail.parameters.to_many = siteGroup;
//mail.parameters.from (with no from address provided the email address of the user triggering the action is used)
mail.parameters.subject="New Post in Discussion: "+document.parent.childAssocs["cm:contains"][0].properties.title;
mail.parameters.template = companyhome.childByNamePath(template);
//execute action against a document
mail.execute(document);
</pre>
<p>This option also will send email to all users in a group…even if their account has been disabled.</p>
<p>Another option when scripting is to dynamically build the notification list – send only to those that are part of the discussion. You can walk the posts through the Child Associations of the topic to build an array of users. For smaller discussion threads there will not be that high of a cost. (Small here is a relative term, as I don’t have any metrics to say what is small).</p>
<pre class="brush: jscript; title: ; notranslate" title="">var emailAddresses = [];
//for p expresion variable
var p, e, a;
//change to use your template
var template = "Data Dictionary/Email Templates/Notify Email Templates/share_discussion_notification.ftl";
function getEmail(person) {
var personNode = people.getPerson(person);
return personNode.properties.email;
}
// build emailAddresses
for (p = 0; p < document.parent.childAssocs["cm:contains"].length; p += 1) {
var user = document.parent.childAssocs["cm:contains"][p].properties.creator;
var email = getEmail(user);
//Is the emailAddress already in the array? If not, add it
if (emailAddresses.length > 0) {
var match = false;
for (e = 0; e < emailAddresses.length; e += 1) {
if (emailAddresses[e] === email) {
match = true;
break;
}
}
if (!match) {
emailAddresses.push(email);
}
} else {
emailAddresses.push(email);
}
}
// create mail action
var mail = actions.create("mail");
mail.parameters.subject = "New Post in Discussion: " + document.parent.childAssocs["cm:contains"][0].properties.title;
mail.parameters.template = companyhome.childByNamePath(template);
//send an email for each address
for (a = 0; a < emailAddresses.length; a += 1) {
mail.parameters.to = emailAddresses[a];
//execute action against a document
mail.execute(document);
}
</pre>
<p>Another option would be to add an aspect to the topic that stores a collection of all the users who have participated in the thread. (You could also add a place to collect watchers, but you’d also need to extend the UI to allow you to capture the users for this list.) The aspect would be added on creation on a new topic. (Use Usernames, so that you can avoid any issues with email addresses changing — Usernames are unmutable). When a user adds a post, their email address is added to the collection for notification.</p>
<pre class="brush: jscript; title: ; notranslate" title="">var creator = document.properties.creator;
//for expresion variable
var u, a;
//change to use your template
var template = “Data Dictionary/Email Templates/Notify Email Templates/share_discussion_notification.ftl”;
function getEmail(person) {
var personNode = people.getPerson(person);
return personNode.properties.email;
}
//Look for match in n:users, if no match add them other wise, ignore the user
function updateUsers(user) {
if (document.parent.properties["n:users"] === null) {
document.parent.properties["n:users"] = [];
document.parent.properties["n:users"].push(user);
document.parent.save();
} else {
var match = false;
for (u = 0; u < document.parent.properties["n:users"].length; u += 1) {
if (document.parent.properties["n:users"][u] === user) {
match = true;
break;
}
}
if (!match) {
document.parent.properties["n:users"].push(user);
document.parent.save();
}
}
}
//Check for notifiable aspect
if (document.parent.hasAspect("n:notifiable") {
//if the user is not already in the n:users property of the notifiable aspect, add them
updateUsers(creator);
//create mail action
var mail = actions.create(“mail”);
mail.parameters.subject = “New Post in Discussion: ” + document.parent.childAssocs["cm:contains"][0].properties.title;
mail.parameters.template = companyhome.childByNamePath(template);
//send an email for each user in n:users
for (a = 0; a < document.parent.properties["n:users"].length; a += 1) {
mail.parameters.to = getEmail(document.parent.properties["n:users"][a]);
//execute action against a document
mail.execute(document);
}
} else {
logger.log("The notifiable aspect has not been added to the topic: " + document.parent.name;
}
</pre>
<p>These options should also take into account disabled accounts: (<code class="language-plaintext highlighter-rouge">person.isAccountEnabled(userName);</code> The problem with this is that <code class="language-plaintext highlighter-rouge">isAccountEnables</code> requires admin privileges. There is no runAs for this kind of javascript so implementing this would require implementing the action in Java, which is outside the scope of this exercise.</p>
<p>Hopefully, I’ve given you some ideas of ways to approach adding discussion notifications. The key here in discovering how to approach this was knowing where to look at how discussions are stored in the repository. In fact this is the key for most any extension to Alfresco: Asking yourself how does this work for this feature, which is similar to what I need, work in Alfresco? Understanding how this is pulled together from looking at the content model, looking at actual content/nodes through the node browser, understanding the relationships between nodes: parent/child and peer and sometimes digging into the source code can help to expand your understanding when looking to extend Alfresco.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-share-discussion-notification">Alfresco: Share Discussion Notification</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on June 02, 2011.</p>https://jared.ottleys.net/alfresco/alfresco-simple-file-diff2010-11-28T00:00:00-07:002010-11-28T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>I’ve heard asked many times by customers and community members if there was a way to diff files in Alfresco and alas there isn’t an OTB way to do this. A month ago the discussion came up again internally. And I thought it might be fun to tackle this as side project just to see if/what was possible. So I took an evening and hammered out a simple Java class that did a comparison between two text files. Once I saw that I had at least the basics (annotate the differences between two files) and had gotten the question of basic possibility/difficulty out of the way I moved on to other projects.</p>
<p>Today almost the entire family is sick so I thought I’d pick up the project again, moving the Java class to a Java Backed web script.</p>
<p>The web script is a simple GET that takes the nodeRef of two files, or two versions of the same file and outputs a simple HTML page that highlights the differences between the two. There are no complex algorithms that take into account shifts in blocks or identifies just the text in a line that has changed. It is a simple line by line comparison of two pieces of content. It is not integrated in to Share or Explorer at this time. I might take that as a separate sick day project (or accept any code contributions to add that).</p>
<p>I’ll admit right off that the code is ugly and repetitive. But this is more of a Proof of Concept than a full production ready implementation (though it could definitely be used as such to provide a quick view of differences).</p>
<p>I’ve also probably bored you with the above so let’s just jump right in before I completely lose you… <img src="http://jared.ottleys.net/wp-includes/images/smilies/icon_wink.gif" alt=";-)" class="wp-smiley" /></p>
<h2 id="using-the-web-script"><strong>Using The Web Script</strong></h2>
<p>The web script is called by the following URL:</p>
<p>alfresco/service/file/{protocol_1}/{identifier_1}/{id_1}/diff/{protocol_2}/{identifier_2}/{id_2}</p>
<p>For real world examples we’ll first look at comparing two files</p>
<p>alfresco/service/file/workspace/SpacesStore/dd83c9f6-81b7-462b-8a1a-1e9a2af251dd/diff/workspace/SpacesStore/ca65d129-6c2c-4ba0-936d-d7626f94f23a</p>
<p>Second comparing two versions of the same file</p>
<p>alfresco/service/file/workspace/SpacesStore/dd83c9f6-81b7-462b-8a1a-1e9a2af251dd/diff/versionStore/version2Store/ca65d129-6c2c-4ba0-936d-d7626f94f23a</p>
<p>What is returned is, as stated above, a simple HTML highlighting the differences</p>
<p style="text-align: center">
<a href="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-28-at-4.46.07-PM.png" rel="lightbox[416]"><img class="aligncenter size-full wp-image-417" src="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-28-at-4.46.07-PM.png" alt="" width="582" height="197" /></a>
</p>
<p>Each line that is different is highlighted in blue. Simple and to the point.</p>
<h2 id="the-code"><strong>The Code</strong></h2>
<p>This is just a little Declarative Web Script that reads the content line by line and then compares the hash of each line to see differences. When a difference is found it is wrapped in HTML to annotate the difference so that when displayed, CSS can take care of highlighting the differences.</p>
<p>A couple of things that I think are important to note:</p>
<ul>
<li><strong>File length:</strong> When comparing two files there is always the possibility that one is longer/shorter than the other. To simplify the comparison, I just append lines with a single space to the shorter file, simplifying any computational work needed for the comparison caused by the difference in length.
<ul>
<li>I mentioned above that the appended line contains a single space. This is done so the that the line appears in the output. <div> tags with no content can be ignored by some browsers. The annotation/presentation uses a combination of <div> and <pre> tags. The space is maintained in a <pre> tag forces the div element to be visible.</li>
</ul>
</li>
<li>**Special Characters: **Because the output for the comparison is targeted for HTML, it is important to escape all characters/strings that could be interpreted by the browser as presentation elements. Apache Commons (included with Alfresco) has classes to help do this.</li>
<li><strong>Gotcha!:</strong> When I was initially testing the code, the file content kept appending files to the previous request. So remember when defining a Collection as a class scoped variable to call clear() on the List to make sure it is empty before it gets reused.</li>
</ul>
<p>This extension is available as an <a href="http://alfresco-simple-file-diff.googlecode.com/files/alfresco-filediff-webscript-0.1.amp">AMP</a>. The source is available in the <a href="http://code.google.com/p/alfresco-simple-file-diff/">Google Code Project</a>.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-simple-file-diff">Alfresco: Simple File Diff</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on November 28, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-alfresco-pdf-tool-kit-insert-pdf-action2010-11-27T00:00:00-07:002010-11-27T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>I’ve taken a bit of Holiday time to update the Alfresco PDF Toolkit. <a href="http://www.unorganizedmachines.com/">Nate</a> has been doing an outstanding job adding <a href="http://www.unorganizedmachines.com/site/software-and-technology/34-software-development/99-watermarking-pdf-documents-with-alfresco">Watermarking</a>, <a href="http://www.unorganizedmachines.com/site/software-and-technology/34-software-development/106-alfresco-pdf-toolkit-digital-signatures">Digital Signatures</a>, <a href="http://www.unorganizedmachines.com/site/software-and-technology/34-software-development/101-encrypting-pdf-documents-with-alfresco">Encryption</a> and cleaning up my messy code. But it was time to add a little bit myself.</p>
<p>So I took sometime this evening to add in one of my planned actions: Insert PDF. This action allows you to insert a PDF into another PDF at a specific page.</p>
<p>This is a pretty straight forward action to test: From the Document Details page of the PDF you want to insert content into, select Run Action (This action can also be run through the rules engine or scripted).</p>
<p>From the list of Actions you’ll want to select Insert PDF.</p>
<p><a href="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-27-at-7.52.02-PM.png" rel="lightbox[406]"><img class="aligncenter size-full wp-image-410" src="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-27-at-7.52.02-PM.png" alt="" width="389" height="104" /></a></p>
<p>Now you will want to set the parameters for the insert. There are 4 parameters for an insert:</p>
<ol>
<li><strong>Name:</strong> This is the name of the new file that will be generated. No extension is needed, it will automatically be added.</li>
<li><strong>Destination:</strong> Space where the new file will be stored.</li>
<li><strong>Insert at page:</strong> Where page 1 of the inserted PDF will start in the targeted PDF.</li>
<li><strong>Insert:</strong> The PDF to insert into the targeted PDF</li>
</ol>
<p><a href="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-27-at-7.52.24-PM.png" rel="lightbox[406]"><img class="aligncenter size-full wp-image-411" src="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-27-at-7.52.24-PM.png" alt="" width="621" height="319" /></a></p>
<p>Finally, you’ll get a message summarizing the action you are taking</p>
<p><a href="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-27-at-7.52.58-PM.png" rel="lightbox[406]"><img class="aligncenter size-full wp-image-412" src="http://jared.ottleys.net/files/2010/11/Screen-shot-2010-11-27-at-7.52.58-PM.png" alt="" width="488" height="62" /></a></p>
<p>See pretty simple!</p>
<p>The latest <a href="http://alfresco-pdf-toolkit.googlecode.com/files/alfresco-pdf-toolkit-0.97.amp">amp</a> and <a href="http://code.google.com/p/alfresco-pdf-toolkit/source/checkout">source</a> can be found at the <a href="http://code.google.com/p/alfresco-pdf-toolkit">Alfresco PDF Toolkit</a> project site.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-alfresco-pdf-tool-kit-insert-pdf-action">Alfresco: Alfresco PDF Tool Kit – Insert PDF Action</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on November 27, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-default-quota-policy2010-09-15T00:00:00-06:002010-09-15T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><em>Updated: Added new tested versions</em></p>
<p>For this post I want to share another policy from recent request from a customer. The project was to help them develop a way to have usage quotas set to a default value when a new user/person was added to Alfresco. (There is an important distinction between the two.) A few months ago I had a discussion about possible ways to implement this kind of functionality with a co-worker and had a few ideas brewing as we started the engagement.</p>
<p>First some background on quotas:</p>
<ul>
<li>The default quota in Alfresco is unlimited. In other words, there is no limit in the amount (total size) of content a user can add/own in Alfresco.</li>
<li>A quota can be set either on creation or at anytime during a users lifetime.
<ul>
<li>From within the UI a quota can be in either GB, MB or KB.</li>
<li>From an API (Web Script, JavaScript, Java) it accepts the size in bytes.</li>
</ul>
</li>
<li>See <a href="http://wiki.alfresco.com/wiki/Usages_and_Quotas">http://wiki.alfresco.com/wiki/Usages_and_Quotas</a> for additional details</li>
</ul>
<p>When developing a behavior I continually reference <a href="http://ecmarchitect.com/">Jeff Potts’s</a> tutorial on <a href="http://ecmarchitect.com/archives/2007/09/26/770">implementing behaviors</a>. His table of available policies is a great quick reference. Of course the definitive source is the Alfresco source code, but not much has changed since Jeff wrote the article.</p>
<p><strong>JavaScript Behavior</strong></p>
<p>The first attempt to implementing this policy was to use a behavior implemented in JavaScript. I used Jeff’s JavaScript example as the seed for my code. In fact there was very little I needed to change (mostly just the business logic). It is a great outline to get you going.</p>
<p>Using JavaScript wasn’t completely successful. The code did work up to a point and that point was setting the quota. There is an undocumented (in the wiki) JS function that can be used to set quotas.</p>
<p>From the People class (note the comment):</p>
<pre class="brush: jscript; title: ; notranslate" title="">/**
* Set the content quota in bytes for a person.
* Only the admin authority can set this value.
*
* @param person Person to set quota against.
* @param quota As a string, in bytes, a value of "-1" means no quota is set
*/
public void setQuota(ScriptNode person, String quota)</pre>
<p>During the transaction the quota was being set, but once the transaction was committed it was lost. The problem being (again see the comments) that the setQuota must be executed by an admin. A quota must be set explicitly by an admin.</p>
<p>The JavaScript API lacks the ability to execute a script as one user but then run specific code in that script as another user. This is done for <a href="http://forums.alfresco.com/en/viewtopic.php?f=6&t=28475&start=0">security reasons</a> (like being able to keep a user from uploading and then executing a malicious script).</p>
<p>While the script did not work as desired it still may be useful for something else. So I’m including links to the <a href="http://svn.ottleys.net/alfresco/misc_scripts/js_behavior/behaviour-context.xml">context file</a> and <a href="http://svn.ottleys.net/alfresco/misc_scripts/js_behavior/behaviour.js">script</a> as part of this post.</p>
<p><strong>Java Behavior</strong></p>
<p>Because we need to set the quota as an admin user, we’ll switch to Java which provides a useful means to run code as one user, and execute specific parts as a different user.</p>
<p>Before we jump to that, let’s talk about some of the specifics of our policy:</p>
<p>First, there is not much difference in the structure needed for this behavior and the <a href="http://code.google.com/p/alfresco-maxversion-policy/">Max Version Policy</a> I wrote about in a <a href="http://jared.ottleys.net/alfresco/alfresco-max-version-policy">previous post</a>. So I used it as a template for this project.</p>
<p>Next, users/people in Alfresco are stored as nodes. If you start to dig into <a href="http://www.alfresco.com/help/webclient/tasks/tuh-admin-nodebrowser.html">the node browser</a> you may be drawn to find your users in the user://alfrecoUserStore store. This is, as the name suggestions, the Alfresco User Store which is used as the native Alfresco authentication source (<a href="http://wiki.alfresco.com/wiki/Authentication_Subsystems#AlfrescoNtlm">alfrescoNTLM</a>). The nodes in this store are of type {http://www.alfresco.org/model/user/1.0}user. Quotas are a property of {http://www.alfresco.org/model/content/1.0}person and are found in the workspace://SpacesStore under system -> people.</p>
<p>Alfresco has no specific policies for user nodes (like an onCreateUser policy), but since they are just nodes we can leverage the onCreateNode policy. Also because they are just nodes, when using an onCreateNode policy we don’t want the policy to be run every time a node is created. We want to target cm:person nodes. Otherwise, we would need to overcomplicate the code with additional tests to only only execute the business logic when the newly created node is of type cm:person. So we can bind our policy to a specific type. Similarly to the max version policy we initialize our behavior and bind it in the init method.</p>
<pre class="brush: java; title: ; notranslate" title="">public void init() {
this.onCreateNode = new JavaBehaviour(this, "onCreateNode", NotificationFrequency.TRANSACTION_COMMIT);
this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.TYPE_PERSON, this.onCreateNode);
}</pre>
<p>In the init() above we bind our policy: QName.createQName(NamespaceService.ALFRESCO_URI, “onCreateNode”), the type: ContentModel.TYPE_PERSON and the behavior: this.onCreateNode together.</p>
<p>Next we need to implement the onCreateNode method from the OnCreateNodePolicy in our new class</p>
<pre class="brush: java; title: ; notranslate" title="">public void onCreateNode(ChildAssociationRef childAssocRef) {</pre>
<p>We can grab our reference to the newly created user node from the ChildAssociationRef parameter.</p>
<pre class="brush: java; title: ; notranslate" title="">final NodeRef user = childAssocRef.getChildRef();</pre>
<p>Notice that it is using the final modifier. Variables from an outer class that are being referenced in an inner class must be defined as final.</p>
<p>For our default policy we also want to allow the default quota to be overwritten. If an admin wants to set a different value for the quota at creation time we need to allow it. So we need to first get the value that was assigned to the user on the node at creation</p>
<pre class="brush: java; title: ; notranslate" title="">long currentQuota = contentUsageService.getUserQuota((String) nodeService.getProperty(user,ContentModel.PROP_USERNAME));</pre>
<p>The default value of no quota is -1. If an admin has set a value for the quota it will be greater than 0.</p>
<pre class="brush: java; title: ; notranslate" title="">if (currentQuota < 0) {
...business logic here...
}</pre>
<p>Because quotas can only be set by an admin we need to use the runAs utility</p>
<pre class="brush: java; title: ; notranslate" title="">AuthenticationUtil.runAs(
new AuthenticationUtil.RunAsWork<Object>() {
public Object doWork() throws Exception {
...code to be run as a different user...
}
}, "admin");</pre>
<p>runAs takes two parameters: The first is the RunAsWork Object, which contains an inner class that has the code that needs to be run as another user. Second the name of the user to execute the code as.</p>
<p>Now in our policy we can set the quota. We’ll use the contentUsageService to set the new users quota, taking the username and the value of the quota as parameters. Also note that default value is in bytes which is read from the spring context file for the OTB default I’ve chosen 2GB (2147483648).</p>
<pre class="brush: java; title: ; notranslate" title="">contentUsageService.setUserQuota((String) nodeService.getProperty(user, ContentModel.PROP_USERNAME), Long.parseLong(defaultQuota));
return user; </pre>
<p>RunAsWork is also looking for a return value which I’ve set as user. We won’t being doing anything with this value so in this case it is an arbitrary return.</p>
<p><strong>Installing</strong></p>
<p>The policy is <a href="http://code.google.com/p/alfresco-defaultquota-policy/downloads/detail?name=alfresco-defaultquota-policy.amp">packaged as an AMP</a> and is downloadable from the <a href="http://code.google.com/p/alfresco-defaultquota-policy/">alfresco-defaultquota-policy project</a> hosted on Google Code. (See <a href="http://wiki.alfresco.com/wiki/Module_Management_Tool">http://wiki.alfresco.com/wiki/Module_Management_Tool</a> for detailed instructions on installing AMPs.)</p>
<p>The amp has been tested with Alfresco Enterprise 3.1.1 to 3.3.2.</p>
<p>I’ve only tested this on Enterprise 3.3.2. I’ll look to expand my testing soon. If you have a chance to try it on another version of Alfresco please let me know. (In that case you’ll need to build the amp from source, changing the version.min/max property to include the version you’re targeting.)</p>
<p><strong>So what next?</strong></p>
<p>While this policy is limited to setting the user quota, your probably thinking what else can I use this for? I know I am! Maybe your using a database or some other none supported user store in Alfresco and you want to populate the user node properties with details from that system. Or you want to add them to specific groups based on some complex business logic. Or interact with external systems when a user is created in Alfresco. What are your ideas?</p>
<p>I also believe that this should work with any external authentication/synchronization of users into Alfresco. I’ve not tested it, but it is the list of things to test next.</p>
<p>I’m always looking for feedback: let me know what worked or didn’t for you.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-default-quota-policy">Alfresco: Default Quota Policy</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on September 15, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-permissions-web-scripts2010-08-26T00:00:00-06:002010-08-26T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>A couple of months back I was asked to write a couple of web scripts to help one of our customers to be able to check and modify permissions for content/spaces in the Alfresco repository. I’ve finally had the chance to spend sometime testing and now writing about them.</p>
<p>The core of the web scripts was quick to write. The fun (more time consuming) part was working with exception handling in javascipt. I know tons of fun right! There are few different ways to use exception handling based on which version of Alfresco you are using. The customer is on Enterprise 3.1 and I wanted to make sure that the web scripts also worked on the more current releases of Alfresco as well. A <a href="https://issues.alfresco.com/jira/browse/ALF-1958">change (re: addition)</a> was made in Enterprise 3.2.1 and Community 3.3 to help simplify exception handeling. I’ll talk about exception handling and these differences in a follow up post. For now let’s talk about these new web scripts.</p>
<h2 id="permissions-get"><strong>permissions GET</strong></h2>
<p>The first web script returns all of the permissions for a specified node.</p>
<p>The URL used is <code class="language-plaintext highlighter-rouge">/alfresco/service/permissions/{store_type}/{store_id}/{id}</code></p>
<p>Where</p>
<p style="padding-left: 30px">
<code>store_type</code>: The type of store you want to query, ex: <code>workspace</code>
</p>
<p style="padding-left: 30px">
<code>store_id</code>: The ID of the store you want to query, ex: <code>SpacesStore</code>
</p>
<p style="padding-left: 30px">
<code>id</code>: The UUID of the node, ex: <code>aed218e8-df44-4865-84cd-0105252f4993</code>
</p>
<p>The above values are joined together to form the nodeRef.</p>
<p>If the node is not found a 404 error will be returned, any missing URI parameters will result in a 400 error and if you don’t have permission to view the node you will get a 401 error.</p>
<p>The web script will return a JSON object that looks like the following:</p>
<pre class="brush: jscript; title: ; notranslate" title="">{ "permissions": [
"ALLOWED;user1;Coordinator",
"ALLOWED;user2;Coordinator"
] ,
"inherit": false }</pre>
<p>The return object lists the permissions in a triplet for that node. The permissions triplet follow this format:</p>
<p style="padding-left: 30px">
<code>[ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION</code>
</p>
<p>It also returns a boolean value indicating if some permissions are inherited from the parent node.</p>
<p>The above example shows two permissions are assigned to the node: the Coordinator permission is given to user1 and user2 on this node. Permissions are not inherited from the parent node.</p>
<h2 id="permissions-post"><strong>permissions POST</strong></h2>
<p>This web script enables you to modify the permissions for a given node</p>
<p>It is called through the same URL as the above web script but as a POST instead of a get: <code class="language-plaintext highlighter-rouge">/alfresco/service/permissions/{store_type}/{store_id}/{id}</code></p>
<p>Again, if the node is not found a 404 error will be returned, any missing URI parameters will result in a 400 error and if you don’t have permission to modify the node, you will get a 401 error.</p>
<p>You must also pass a JSON object containing the permissions that are being changed, deleted or added.</p>
<pre class="brush: jscript; title: ; notranslate" title="">{ permissions: [
"REMOVE;user3;All",
"REMOVE;user2;All",
"ADD;user4;Coordinator",
"ADD;GROUP_usergroup1;Consumer"
] ,
"inherit": false }
</pre>
<p>The above example uses the following triplet to define a permission</p>
<p style="padding-left: 30px">
<code>[ADD|REMOVE];[USERNAME|GROUPNAME];PERMISSION</code>
</p>
<p>Where the values are defined as:</p>
<p style="padding-left: 30px">
<code>ADD | REMOVE</code>: Do you want to add or remove the permission for this user/group? Any other value passed will result in a 400 error.
</p>
<p style="padding-left: 30px">
<code>USERNAME | GROUPNAME</code>: The user or group you want the permission to be added or removed for. Group names must be prefixed by GROUP_. Unknown users or groups will result in a 400 error.
</p>
<p style="padding-left: 30px">
<code>PERMISSION</code>: The supported permissions options are defined in<br /> <code>org.alfresco.service.cmr.security.PermissionService</code> or through custom extension to the permission model. Unknown permissions will result in a 400 error.
</p>
<p>The object can also contain an optional inherit permission value to specifying if the permissions for this node should be inherited from the parent node. Without the inherit option, the current value for the node is maintained. Inherited permissions can not be removed from a node.</p>
<p>The return format is the same as the return format of the permissions GET web script above.</p>
<p>This web script is also transactional: any errors will result in the node being returned to the state before the call was made. (The exception handling in the controller was added for these conditions.)</p>
<p>These scripts can be installed as an AMP. The <a href="http://code.google.com/p/alfresco-permissions-webscripts/source/checkout">code</a> and <a href="http://code.google.com/p/alfresco-permissions-webscripts/downloads/list">AMPs</a> are hosted in the <a href="http://code.google.com/p/alfresco-permissions-webscripts/">alfresco-permissions-webscripts</a> project on Google Code. The code is available for either <a href="http://code.google.com/p/alfresco-permissions-webscripts/downloads/detail?name=alfresco-permissions-webscripts-pre.3.2.1.amp">pre-3.2.1 (starting with 3.1)</a> or <a href="http://code.google.com/p/alfresco-permissions-webscripts/downloads/detail?name=alfresco-permissions-webscripts.amp">3.2.1 to 3.3.1</a>. These are all Enterprise releases numbers. The web scripts have been tested against these releases. There may not be any need to modify the web scripts for Community releases (except for the min and max version numbers in the module.properties file). Pre community 3.3 should use the pre 3.2.1 release. No community releases have been tested. (If you try these on a community releases, please comment either here or in the Google Code Project.)</p>
<p>In a follow up post, I’ll cover exception handling with JavaScript.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-permissions-web-scripts">Alfresco: Permissions Web Scripts</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on August 26, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-jmx-from-the-command-line2010-08-03T00:00:00-06:002010-08-03T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><a href="http://java.sun.com/javase/6/docs/technotes/guides/jmx/index.html">JMX</a> is great. <a href="http://www.alfresco.com">Alfresco</a> and <a href="http://wiki.alfresco.com/wiki/JMX">JMX</a> is awesome. I’ve written before about <a href="http://jared.ottleys.net/alfresco/tunneling-debug-and-jmx-for-alfresco">configuring Alfresco to use tunneling to connect JMX & debuggers</a> to servers that don’t allow access to the higher numbered ports (or to only a few ports). Let’s add another cool tool. (JMX is an Enterprise only feature of Alfresco.)</p>
<p>The <a href="http://wiki.alfresco.com">Alfresco wiki</a> covers a few of the <a href="http://wiki.alfresco.com/wiki/JMX#Connecting_through_JMX_Consoles_.2F_JSR-160">clients</a> that are available out there. Let’s add another type to the list: JMX from the command line. There are a <a href="http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=jmx+command+line">couple of options</a> for us to choose from. I am partial to j<a href="http://www.cyclopsgroup.org/projects/jmxterm/index.html">mxterm</a> from <a href="http://www.cyclopsgroup.org/">CyclopsGroups.org</a></p>
<p>Jmxterm is an opensource JMX Client (<a href="http://sourceforge.net/projects/cyclops-group/files/jmxterm/">download</a>). It supports auto completion, history browsing and scripting. In a word: cool.</p>
<p>Let’s jump in…</p>
<p>First thing you need to do with the client is connect</p>
<p>With jmxterm you can pass a connection string via the jar to connect to Alfresco</p>
<p style="padding-left: 30px">
<code>java -jar jmxterm-1.0-alpha-4-uber.jar -l \ service:jmx:rmi:///jndi/rmi://<host>:50500/alfresco/jmxrmi -p <password> -u <user></code>
</p>
<p>Or, you can connect via the interactive shell</p>
<p style="padding-left: 30px">
<code>java -jar jmxterm-1.0-alpha-4-uber.jar</code>
</p>
<p>Next, using the <code class="language-plaintext highlighter-rouge">open</code> command connect to Alfresco</p>
<p style="padding-left: 30px">
<code>$>open service:jmx:rmi:///jndi/rmi://localhost:50500/alfresco/jmxrmi -p change_asap -u \ controlRole<br />
#Connection to service:jmx:rmi:///jndi/rmi://localhost:50500/alfresco/jmxrmi is opened</code>
</p>
<p>Or, using the <code class="language-plaintext highlighter-rouge">jvms</code> command (with the appropriate user permissions you can list the running java processes on the machine jmxterm is running on).</p>
<p style="padding-left: 30px">
<code>$>jvms<br />
94822 ( ) - jmxterm-1.0-alpha-4-uber.jar<br />
13157 (m) - org.apache.catalina.startup.Bootstrap start</code>
</p>
<p>And once again use the <code class="language-plaintext highlighter-rouge">open</code> command to connect</p>
<p style="padding-left: 30px">
<code>$>open 13157 -u controlRole -p change_asap<br />
#Connection to 13157 is opened</code>
</p>
<p>Now that you are connected let’s perform a simple operation, finding out who is logged into Alfresco and then disconnect one of the users.</p>
<p>First you’ll list the domains that are available</p>
<p style="padding-left: 30px">
<code>$>domains<br />
#following domains are available<br />
Alfresco<br />
Catalina<br />
JMImplementation<br />
Users<br />
axis<br />
com.sun.management<br />
connector<br />
java.lang<br />
java.util.logging<br />
log4j</code>
</p>
<p>The bean you need is in the <code class="language-plaintext highlighter-rouge">Alfresco</code> domain</p>
<p style="padding-left: 30px">
<code>$>domain Alfresco<br />
#domain is set to Alfresco</code>
</p>
<p>You can look up the beans within the domain by using the <code class="language-plaintext highlighter-rouge">beans</code> command. There are quite a few beans in the <code class="language-plaintext highlighter-rouge">Alfresco</code> domain, so let’s connect directly to the one that you need: <code class="language-plaintext highlighter-rouge">RepoServerMgmt</code>. (Don’t forget jmxterm support auto completion.)</p>
<p style="padding-left: 30px">
<code>$>bean Alfresco:Name=RepoServerMgmt<br />
#bean is set to Alfresco:Name=RepoServerMgmt</code>
</p>
<p>Now that you have the bean, you can now find out what it does by using the <code class="language-plaintext highlighter-rouge">info</code> command</p>
<p style="padding-left: 30px">
<code>$>info<br />
#mbean = Alfresco:Name=RepoServerMgmt<br />
#class name = org.alfresco.repo.admin.RepoServerMgmt<br />
# attributes<br />
%0 - LinkValidationDisabled (boolean, r)<br />
%1 - MaxUsers (int, r)<br />
%2 - ReadOnly (boolean, r)<br />
%3 - TicketCountAll (int, r)<br />
%4 - TicketCountNonExpired (int, r)<br />
%5 - UserCountAll (int, r)<br />
%6 - UserCountNonExpired (int, r)<br />
# operations<br />
%0 - int invalidateTicketsAll()<br />
%1 - int invalidateTicketsExpired()<br />
%2 - void invalidateUser(java.lang.String p1)<br />
%3 - [Ljava.lang.String; listUserNamesAll()<br />
%4 - [Ljava.lang.String; listUserNamesNonExpired()<br />
#there's no notifications</code>
</p>
<p>There are lots of things you can do here, you can get attributes, you can set attributes, you can run opertaions.</p>
<p>Looking at the returned attributes you can tell a few things: the datatype of each attribute and if you can change it. In this case none of our attributes can be modified, they are all read only (r). ReadWrite would be signified by rw.</p>
<p>Operations are just like Java functions: they can have a return type and you might be able pass parameters to them.</p>
<p>It can also list notifcations, but there is no mechanism (that I have found yet) in jmxterm to subscribe or unsubscribe to them.</p>
<p>Let’s do some more work:</p>
<p>First let’s see how many users are logged in.</p>
<p style="padding-left: 30px">
<code>$>get UserCountNonExpired<br />
#mbean = Alfresco:Name=RepoServerMgmt:<br />
UserCountNonExpired = 2;</code>
</p>
<p>And now let’s find out who these two users are</p>
<p style="padding-left: 30px">
<code>$>run listUserNamesNonExpired<br />
#calling operation listUserNamesNonExpired of mbean Alfresco:Name=RepoServerMgmt<br />
#operation returns:<br />
[ System, admin ]</code>
</p>
<p>So you have 2 user currently in Alfresco: System and admin</p>
<p>Let’s log out the admin user. (Look back at the list from the <code class="language-plaintext highlighter-rouge">info</code> command.) You can either invalidate all of the authenticated users tickets or we can invalidate named user. Since you just want to remove one user let’s invalidate the admin users session. (The System user is a special account, so you will ignore it.)</p>
<p style="padding-left: 30px">
<code>$>run invalidateUser admin<br />
#calling operation invalidateUser of mbean Alfresco:Name=RepoServerMgmt<br />
#operation returns:<br />
null</code>
</p>
<p>Remember that this operation does not return anything so jmxterm returns <code class="language-plaintext highlighter-rouge">null</code>.</p>
<p>Let’s now check and see if the admin user is still logged in.</p>
<p style="padding-left: 30px">
<code>$>run listUserNamesNonExpired<br />
#calling operation listUserNamesNonExpired of mbean Alfresco:Name=RepoServerMgmt<br />
#operation returns:<br />
[ System ]</code>
</p>
<p>You’ve now run through some basic operations: connecting to Alfresco, listing and selecting the domain, listing and selecting the bean, discovering what the bean can do, getting an attribute, running an operation.</p>
<p>The last things to cover are scripting/non-interactive mode and setting properties.</p>
<p>From time to time you might see the need, to lock Alfresco down by setting it to Read Only mode. The following two commands will help us do this.</p>
<p>First let’s check the current state:</p>
<p style="padding-left: 30px">
<code>$ echo get -s -b Alfresco:Name=RepoServerMgmt ReadOnly | java -jar jmxterm-1.0-alpha-4-uber.jar -l service:jmx:rmi:///jndi/rmi://localhost:50500/alfresco/jmxrmi -p change_asap -u controlRole -v silent -n</code>
</p>
<p>When passing commands to jmxterm, you need to echo the command into the interpreter. So the above command passes the jmxterm get command. we pass it a <code class="language-plaintext highlighter-rouge">-s</code> since we just want the value and not the full expression (<code class="language-plaintext highlighter-rouge">ReadOnly = <value>;</code>) returned <code class="language-plaintext highlighter-rouge">-b</code> names the bean and finally the attribute we are querying. On the jmxterm side we pass the connection information (connection string or PID, username and password), set the verbosity of the execution, and tell it that it should run in non-interactive mode.</p>
<p>In this case the command returns the value <code class="language-plaintext highlighter-rouge">false</code>. So the value of <code class="language-plaintext highlighter-rouge">ReadOnly</code> is <code class="language-plaintext highlighter-rouge">false</code> — Alfresco is in Read/Write mode.</p>
<p>Now let’s put it in ReadOnly mode</p>
<p style="padding-left: 30px">
<code>$ echo set -b Alfresco:Category=sysAdmin,Type=Configuration,id1=default server.transaction.allow-writes false | java -jar jmxterm-1.0-alpha-4-uber.jar -l service:jmx:rmi:///jndi/rmi://localhost:50500/alfresco/jmxrmi -p change_asap -u controlRole -v silent -n</code>
</p>
<p>This follow pretty much the same pattern. The <code class="language-plaintext highlighter-rouge">ReadOnly</code> attribute is a read only property, so to change Alfresco into Read Only mode you set the value of <code class="language-plaintext highlighter-rouge">server.transaction.allow-writes</code> to <code class="language-plaintext highlighter-rouge">false</code>. The set will not return any value. You can then check the current state of the repository by running the previous command and then when you are ready to put the repository back in Read/Write mode change the value of the set command on <code class="language-plaintext highlighter-rouge">server.transaction.allow-writes</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-jmx-from-the-command-line">Alfresco: JMX from the command line</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on August 03, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-max-version-policy2010-07-23T00:00:00-06:002010-07-23T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><em>UPDATE: Updated code examples to match updates to source code</em></p>
<p>As part of a POC for a customer I was asked to write an extension that allowed them to control the total number of versions allowed per versioned content. (Download links at bottom of page)</p>
<p>Alfresco has a strong versioning story, that gives you the ability to version any content stored in the repository, no matter what the file type. Versions are full files and not diffs of the files. Alfresco gives you the ability to have both major and minor versions of content. Versions can be created/updated by checkout/checkin, by rule, through any interface or through script/APIs.</p>
<p>Alfresco also provides the ability to apply behaviors / policies to content/metadata within the repository. You can think of these as event listeners, that allow you to take custom actions based on what is happening within the repository. [Jeff Potts][1] has written an excellent [tutorial on creating behaviors][2], with examples for both Java and javascript. (Other resources include: )</p>
<p>There are 4 versioning policies that we can work with:</p>
<ul>
<li>beforeCreateVersion</li>
<li>afterCreateVersion</li>
<li>onCreateVersion</li>
<li>calculateVersionLabel</li>
</ul>
<p>(For an [example of a calculateVersionLabel policy][3] look at this post and the accompanying code by [Peter][4] [Monks][5].)</p>
<p>For this policy we are going to use afterCreateVersion: after a version is created we want to remove any version that puts us past the max version value.</p>
<p>You start by implementing the [policy interface][6] for the policy you want to apply.</p>
<p style="padding-left: 30px">
<code>public class MaxVersionPolicy implements AfterCreateVersionPolicy</code>
</p>
<p>Adding the method implemented by the interface</p>
<p>` `</p>
<p>` `</p>
<p>`</p></p>
<pre style="padding-left: 30px">public void afterCreateVersion(
NodeRef versionableNode,
Version version)</pre>
<p>`
We also need to [bind][7] the [behavior][8] to the policy and we need to do this when the class is loaded by the springframework. We wrap this registration in an init() method
` `
` `
`</p>
<pre style="padding-left: 30px"> public void init() {
this.afterCreateVersion = new JavaBehaviour(this, "afterCreateVersion",
NotificationFrequency.TRANSACTION_COMMIT);
this.policyComponent.bindClassBehaviour(QName.createQName(
NamespaceService.ALFRESCO_URI, "afterCreateVersion"),
MaxVersionPolicy.class, this.afterCreateVersion);
}</pre>
<p>`
The init method is then called when spring loads the bean
` `
` `
`</p>
<pre> <bean id="maxVersion" class="org.alfresco.extension.versioning.MaxVersionPolicy" init-method="init">
<property name="policyComponent">
<ref bean="policyComponent" />
</property>
<property name="versionService">
<ref bean="versionService" />
</property>
<!-- The max number of versions per versioned node -->
<property name="maxVersions">
<value>10</value>
</property>
</bean></pre>
<p>`
Now to the meat of the policy, enforcing the removal of versions.
First we we have our maxVersion property. This is set in the springbean (see above) and read when the bean is loaded. (You can overwrite the default value by copying the maxversionpolicy-context.xml file into the extensions directory and changing the value of the maxVersion property)
` `
` `
`</p>
<pre> public void setMaxVersions(int maxVersions) {
this.maxVersions = maxVersions;
}</pre>
<p>`
Next we want to remove any version that puts us over the max
` `
` `
`</p>
<pre> @Override
public void afterCreateVersion(NodeRef nodeRef, Version version) {
VersionHistory versionHistory = versionService
.getVersionHistory(nodeRef);
// If the current number of versions in the VersionHistory is greater
// than the maxVersions limit, remove the root/least recent version
if (versionHistory.getAllVersions().size() > maxVersions) {
logger.debug("Removing Version: "
+ versionHistory.getRootVersion().getVersionLabel());
versionService.deleteVersion(nodeRef, versionHistory
.getRootVersion());
}
}</pre>
<p>`
We first get the [versionHistory][9] for the node and check it against the maxVersion property. If we have more versions than the limit we delete the [least recent][10] [version][11] from the version history.
<div id="attachment_363" style="width: 803px" class="wp-caption aligncenter">
<img class="size-full wp-image-363" src="http://jared.ottleys.net/files/2010/07/Screen-shot-2010-07-23-at-12.45.06-AM1.png" alt="10 Version Limt" width="793" height="292" /><p class="wp-caption-text">
Notice: 10 total versions; least recent version is 1.1; no pagination!
</p>
</div>
> **Q:** Why are you using an if statement, instead of looping through all of the versions. I have a few (many) more versions over the limit I want to impose?
>
> **A:** A while statement would be more efficient in removing everything above the limit, but the versionHistory object isn’t being updated with each delete in a loop (I tried) until the policy has completely run. Luckily, as you will find as you implement custom behaviors, they get called a lot! So while a single update will result in a single version added, you will actually see that the behavior is called multiple times for that one action (In testing I saw the behavior being called a minimum of 7 times). While you might not clear out every version over the limit with a single update, you will see a good many of them removed. If you want to bring every down to the limit you could create a “cleaner” extension that could go in and remove all of the versions over the limit (this would be a very intensive/costly operation depending on the amount of content in your repository and the size of your version history – There several different strategies for this). You could then use this extension to enforce that limit.
> **Q:** Does this affect all versioned content in the repository? What if I only want it to work on some content?
>
> **A:** Yes, by default this policy is being applied to all versioned content, but you could add in checks to look for a specific aspect, parent space name, property, etc. before passing through the delete code.
> **Q:** Compliance regulations/laws don’t allow me to delete the versions, but require me keep them for X years. How can I archive versions?
>
> **A:** There are probably several different ways to handle this. One way could be to use Alfresco’s [content store selector][12]. The content store selector allows you to configure multiple underlying filesystem locations for your content. To the end user all of the content appears to come from the same location, while the content itself lives in different disc systems. The idea would be to set up a secondary store that would act as the archive. When your trigger is reached, be it a property (status [property or aspect], date, etc.), a version number/count, etc. have the policy move the content to the archive space structure (The archive will need to have the cm:storeSelector aspect set to mark it to use the secondary store). Because we are working from the least recent version, the 1.0 version of the content, it becomes the first version in the archive, each subsequent version, is added as a version for the content, version numbers are then maintained in the archive. I would also add an aspect to the original node that contains a pointer/aspect to the archived version, for reference. Then delete the least recent version of the original versioned node.
Google Code Project: <http://code.google.com/p/alfresco-maxversion-policy/>
Download [Alfresco Max Version Policy AMP][13]
Tested on Alfresco (Enterprise) 3.2 – 3.3.1
[1]: http://ecmarchitect.com/
[2]: http://ecmarchitect.com/images/articles/alfresco-behavior/behavior-article.pdf
[3]: http://blogs.alfresco.com/wp/pmonks/2009/11/03/version-baselining/
[4]: http://blogs.alfresco.com/wp/pmonks/
[5]: http://twitter.com/pmonks
[6]: http://dev.alfresco.com/resource/docs/java/repository/org/alfresco/repo/version/VersionServicePolicies.OnCreateVersionPolicy.html
[7]: http://dev.alfresco.com/resource/docs/java/repository/org/alfresco/repo/policy/PolicyComponent.html
[8]: http://dev.alfresco.com/resource/docs/java/repository/org/alfresco/repo/policy/JavaBehaviour.html
[9]: http://dev.alfresco.com/resource/docs/java/repository/org/alfresco/service/cmr/version/VersionHistory.html
[10]: http://dev.alfresco.com/resource/docs/java/repository/org/alfresco/service/cmr/version/VersionService.html
[11]: http://dev.alfresco.com/resource/docs/java/repository/org/alfresco/service/cmr/version/Version.html
[12]: http://wiki.alfresco.com/wiki/Content_Store_Selector
[13]: http://code.google.com/p/alfresco-maxversion-policy/downloads/detail?name=alfresco-maxversion-policy.amp
</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-max-version-policy">Alfresco: Max Version Policy</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on July 23, 2010.</p>https://jared.ottleys.net/alfresco/updated-tunneling-debug-and-jmx-for-alfresco2010-06-11T00:00:00-06:002010-06-11T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>Back in February I wrote a post on <a href="http://jared.ottleys.net/alfresco/tunneling-debug-and-jmx-for-alfresco">Tunneling Debug and JMX with Alfresco</a>. Here are a few updates to that post:</p>
<p>0/ For JMX: From Alfresco 3.2 sp1 (enterprise release) on you no longer need to add the custom-core-services-context.xml file. Instead in the alfresco-global.properties file add monitor.rmi.services.port=50508 to set the static port.</p>
<p>1/ How can I get this work on Windows? I’ve tested with two options: <a href="http://cygwin.com/">Cygwin</a> or <a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/">Putty</a>.</p>
<p style="padding-left: 30px">
Cygwin: this is option is the most straight forward. Install Cygwin. On the Select Packages screen do a search for openssh and select it for install.
</p>
<p style="padding-left: 30px">
The same ssh commands will work for creating tunnels under a Cygwin shell that you can use with Linux or Max OS X.
</p>
<p style="padding-left: 30px">
Putty: The only way I’ve been successful with connecting JMX with putty was by using the <a href="http://the.earth.li/~sgtatham/putty/0.60/htmldoc/Chapter7.html#plink">plink</a> command line executable. The graphical interface doesn’t allow you set the first hostname that is needed for the second tunnel. plink gets this done.
</p>
<p style="padding-left: 30px">
Let’s take a look at some real examples…
</p>
<p style="padding-left: 30px">
For debug:
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
On the server:
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
Add the following to your <code style="padding: 0px;margin: 0px">alfresco.sh</code> file:
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
<code style="padding: 0px;margin: 0px">export JAVA_OPTS="${JAVA_OPTS} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8082"</code>
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
The <code style="padding: 0px;margin: 0px">address</code> parameter sets the port you want the debugger to listen on. Adding this line, will require you to restart Alfresco/tomcat for it to become active.
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
On the client:
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
Using standard port forwarding with ssh will enable Java debugging using plink
</p>
<p><code style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">plink <user>@<ip/host/dns address> -L 2000:localhost:8082 -N</code></p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 60px">
The debugger is then set to connect to <code>localhost</code> using port <code>2000</code>
</p>
<p style="margin-top: 0px;margin-right: 0px;margin-bottom: 15px;margin-left: 0px;line-height: 18px;padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 30px">
For JMX:
</p>
<p style="padding-left: 60px">
On the server:
</p>
<p style="padding-left: 60px">
Modify <code>alfresco.sh</code>: set <code>-Dcom.sun.management.jmxremote</code> equal to <code>true</code>. Append <code>-Djava.rmi.server.hostname=dummyhost</code> to the <code>JAVA_OPTS</code>.
</p>
<p style="padding-left: 60px">
Add <code>monitor.rmi.services.port=50508</code> to <code>alfresco-global.properties</code>
</p>
<p style="padding-left: 60px">
On the client:
</p>
<p style="padding-left: 60px">
Add <code>127.0.0.1 dummyhost</code> to <code>C:\Windows\System32\drivers\etc\hosts</code>
</p>
<p style="padding-left: 60px">
Using plink from the command line, run the following two commands:
</p>
<p style="padding-left: 60px">
<code>plink <user>@<ip/dns of server> -L 2001:localhost:50500 -N</code>
</p>
<p style="padding-left: 60px">
<code>plink <user>@<ip/dns of server> -L dummyhost:50508:localhost:50508 -N</code>
</p>
<p style="padding-left: 60px">
Now you can connect to Alfresco using a jmx console, like jconsole (ships with jre/jdk).
</p>
<p style="padding-left: 60px">
the connection string will look like this:
</p>
<p style="padding-left: 60px">
<code>service:jmx:rmi:///jndi/rmi://localhost:2001/alfresco/jmxrmi</code>
</p>
<p style="padding-left: 60px">
Unless you’ve changed them, the default username and password are <code>controlRole</code> and <code>change_asap</code>
</p>
<p><a href="https://jared.ottleys.net/alfresco/updated-tunneling-debug-and-jmx-for-alfresco">UPDATED: Tunneling Debug and JMX for Alfresco</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on June 11, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-simplegis2010-05-31T00:00:00-06:002010-05-31T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><em><strong>Update:</strong> Share in Alfresco 4.0 now supports google maps integration out of the box.</em></p>
<p>When I worked Pre-Sales here at [Alfresco][1] I had, on occasion, the chance to talk about integrating Alfresco with a [GIS (G][2][eographic Information System)][2]. I don’t have a lot of experience with these types of systems (actually none) but I always believed that integration was possible. (It falls into one of those categories where we believe that it is possible, but we have never actually seen it done.) Or even that a simple type of integration could be done with [Google Maps][3] to provide basic information.</p>
<p>I started playing with the Google Maps APIs almost 6 months ago and was able to throw together a rough POC, but felt that there was still more that I could do. I’ve gotten closer to what I envisioned and a have put together a starting example, but there is still a lot of work to do, including working around issues from the underlying APIs. (ie one of my examples will not work in some versions of Firefox or IE…I’m working on these, but believe that the answer may actually be in the code of either the browser or the Maps API.)</p>
<p>You should note that my example is based off of US addresses. If you are outside of the US you may need to customize these further to meet your needs, but the underlying code should work fine.</p>
<p><strong>Content Map</strong></p>
<p>My first attempt, used an aspect to contain the address. The fields I looked at are: address, city, state and zip code. Any of these, or any combination of these, can be passed to Google to get the coordinates. In this release, I’ve stuck with this design with a little modification. The map that is rendered for the document (as a presentation template in the document details page) uses a static google map. This is quick and easy, it is basically an image tag that has several parameter passed as part of the src. This approach works find for our simple content item map, but has some limitations, for example you can’t design it provide more in-depth information like the little pop up information windows.</p>
<p>In both of these example maps (the document based map and the space map that we will discuss next) it requires some additional changes to the core Alfresco App. I needed to do two things: make a call within my web script to an external resource, in this case Google Maps, to take an address turn it into map coordinates. And have the presentation template call the web script. With a little help from Yong Qu, I was able to address both of these. For the first issue you can enable the ‘remote’ object in javaScript. This is enabled by default for Alfresco Share, but not in Alfresco Explorer. Yong has an easy to follow, [example on his blog][4], on how to do this.</p>
<p>To use the remote object then, we make a simple set of calls like below:</p>
<pre style="padding-left: 30px">uri = "http://maps.google.com/maps/geo?q="+encodeURI(fullAddress)+"&key="+key+"&sensor=false&output=json"
var connector = remote.connect("http");
var result = connector.call(uri);
var obj = eval("("+result+")");
coordinates = obj.Placemark[0].Point.coordinates[1]+","+obj.Placemark[0].Point.coordinates[0];</pre>
<p>You should also note that the json that is returned to our call is returned as a string. We will need to turn it into an javascript object to work with it by using the javaScript eval function. Once we have done this, we can working on the json as a standard object.</p>
<p>This object, remote, however us not enabled for use in presentation templates. So again, borrowing from Yong, I used YUI (which we ship and use in Alfresco) to allow me to make the call out to get the map, via web script.</p>
<p>Our YUI code, again is pretty straight forward…make the call to the web script, set the returned code to be the html of a specified div tag:</p>
<pre style="padding-left: 30px"><script src="/alfresco/yui/yahoo.js" type="text/javascript"></script>
<script src="/alfresco/yui/event.js" type="text/javascript"></script>
<script src="/alfresco/yui/connection.js" type="text/javascript"></script>
<script type="text/javascript">
var handleSuccess = function(o){
var div = document.getElementById('mapping');
div.innerHTML = o.responseText; }
var handleFailure = function(o){
var div = document.getElementById('mapping');
div.innerHTML = "Map not available"; }
var callback = {
success: handleSuccess,
failure: handleFailure,
argument: {} }
var request = YAHOO.util.Connect.asyncRequest('GET', '/alfresco/wcservice/map/workspace/SpacesStore/${document.id}', callback);
</script></pre>
<p>We want to incude the js references as the beginning. This enables us to make successful calls to this script without the wrapper of our presentation template.</p>
<p>Next, notice the url that we are calling, 1/ the call is relative to the application we are running. 2/We are using wcservice. This make it so that we are not prompted to login each time the web script is called.</p>
<p style="text-align: center">
<img class="size-full wp-image-339 aligncenter" src="http://jared.ottleys.net/files/2010/06/Screen-shot-2010-05-31-at-11.07.21-PM.png" alt="Screen shot 2010-05-31 at 11.07.21 PM" width="659" height="266" />
</p>
<p>As I mentioned above, this is the easer part especially since the hard parts were already thought out by Yong….</p>
<p><strong>Space Map</strong></p>
<p>For the second map, I initially wanted it to be a presentation template, with an amalgamation of spaces & content, on a single map.</p>
<p>The static maps API is a not a fit here. I wanted to have a map that allowed me not to just specify the location for each child in a space, but also display a limited set of specific information about that piece of content. ** Note:** This does mean that you may need to develop a specific “folder” based taxonomy to better organize content by location.</p>
<p>As I said above, my original attempts here were to try and do everything in a presentation template. It would make multiple calls through YUI to get coordinates for the children of the space. But YUI here becomes the bottleneck, especially since, I could not find a way to pass information out of the YUI Connection Managers callbacks. This greatly limits the design…and ends up nesting calls to a depth of <em>n</em> as we loop through all of the children in a space.</p>
<p>One way to overcome this would be to include in the model/aspect the coordinates. This would reduce the YUI calls to zero. One problem with this approach would be the changes to address need to result in updates to those coordinates. This can be achieved through a behavior, watching the onUpdateProperties method for changes on those nodes. So for this first attempt, I’ve decided to skip this. I’ll revisit this in an upcoming revision to the code.</p>
<p>I started my new web script by moving the logic that I worked on from the presentation template to this web script. This was pretty straight forward. We reused the remote object we enabled from before to pass our location (address string) back to Google. I ignore 99% of what is returned in the json because what we are really after is the coordinates that we can pass to the Google Maps javaScript API. I choose the javaScript API this time, because it allows me to create infoWindows where I can present the user with some basic metadata about the content along with links to either download or browse to the content. I’m not much a UI designer so my poor attempts at layout are sure to make some squirm.</p>
<p>One of the things that was useful in this approach was to pass javaScript Associative Arrays to Freemarker. This allowed me to specify the properties that I wanted/needed directly from witha larger sequence of objects without needing use multiple loops.</p>
<p>This was pretty straight forward: I created an array in my controller. At each element in the array, I created a new Array where I stored my values referenced by a key. I could then in the FTL, start my list, and then reference each child by key name instead of looping again.</p>
<pre>var big = new Array();
//some code that ends of looping over something
//take our properties/metadata stick them in an array
var _child = new Array();
_child["nodeID"] = child.id;
_child["location"] = _location;
_child["latitude"] = _latlong["latitude"];
_child["longitude"] = _latlong["longitude"];
.push(_child);
//end loop
model.big = big</pre>
<p>Now in the ftl:</p>
<pre><#list big as b>
var _latlng${child_index} = new google.maps.LatLng(${child["latitude"]},${child["longitude"]});
</#list></pre>
<p>When we query for the specific node we want to work with where the value we want to pass to the query is in the Associative Array, we need to first clean the variable up, then it will work without throwing an error:</p>
<pre><#assign ref = child["nodeID"]>
<#list companyhome.childrenByLuceneSearch["ID:workspace\\:\\/\\/SpacesStore\\/${ref}"] as _child></pre>
<p style="text-align: center">
<img class="size-full wp-image-340 aligncenter" src="http://jared.ottleys.net/files/2010/06/Screen-shot-2010-06-01-at-1.26.00-AM.png" alt="Screen shot 2010-06-01 at 1.26.00 AM" width="750" height="255" />
</p>
<p>In my design I planned to reuse to the same pattern of using YUI within the presentation template to make the call to the web script, however when attempting to write the generated javaScript to the div tag where the map was to be displayed, I was reminded that the javaScript was never executed. It was treated as a string. It is possible to get around this by using the javaScript eval function to execute the script, but this means turning the content retuned into a big string with lots of escapes…This would kill one of my design choices of making it possible to make direct calls to the web script and have it return a map that could be embedded in different things or be used stand alone. It could also be a big performance hit depending on how often the looip was made. Now, I could go back and write into the content branches to turn areas of the web script ftl on and off as a big sting, but this would over complicate the ftl. This lead to the simplest fallback: have the presentation template reference the web script as part of an iFrame. This worked nicely, but it helped to highlight what I thought was an issue with using the eval function: there is a bug in the newest release of the maps API: it was also happening in the iFrame on IE and Firefox. For the moment I’ll let the script stand, it does work in Safari and Chrome, but it leaves out the major browsers.</p>
<p style="text-align: center">
<p style="text-align: center">
<img class="size-full wp-image-341 aligncenter" src="http://jared.ottleys.net/files/2010/06/Screen-shot-2010-06-01-at-1.26.52-AM.png" alt="Screen shot 2010-06-01 at 1.26.52 AM" width="772" height="283" />
</p>
<p>
<strong>How to use</strong>
</p>
<p>
We are still a bit early for heavy use of our plug. We need to work out a few caching issues, the display issues are IE and Firefox, and modification of core Alfresco configs.
</p>
<p>
For this initial release I won’t provide an amp. I’m want people to know that they will need to dig into this to learn it and extended it. I’d also like to transfer this to be used in Share.
</p>
<p>
<a href="http://code.google.com/p/alfresco-simplegis/source/checkout">The source code</a> is available at the <a href="http://code.google.com/p/alfresco-simplegis/">Google Code Project — alfresco-simplegis</a>.
</p>
<p>
If you want to call the web scripts directly:
</p>
<p>
For the document map use: /map/{store_type}/{store_id}/{id}
</p>
<p>
For the Space map use: /map/coordinates/{store_type}/{store_id}/{id}
</p>
<p>
Both of these will require that the space/content have the mappable aspect applied along with at least the zipcode applied.
</p>
<p>
Spaces can control the default zoom level for their maps. By default that value is 8. You should adjust it based on your need.
</p>
<p>
Document maps are static: there is no zoom or pan. Space maps support both zoom and pan.
</p>
<p>
<strong>What next?</strong>
</p>
<p>
This project is still early in its development. If you have ideas on how to overcome the Firefox or IE issues, your help would be appreciated. I’m also going to be tackling the “caching” issue shortly. This seems to me to be a useful feature. Along with a good understanding of how you can script behaviors for properties when they are updated.
</p>
[1]: http://www.alfresco.com
[2]: http://en.wikipedia.org/wiki/GIS
[3]: http://code.google.com/apis/maps/
[4]: http://drquyong.com/myblog/?p=49
</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-simplegis">Alfresco: SimpleGIS</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on May 31, 2010.</p>https://jared.ottleys.net/alfresco/web-script-modified-content-list2010-05-17T00:00:00-06:002010-05-17T00:00:00-06:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>For a recent customer engagement I was asked to write a web script which they’ve agreed to let me share.</p>
<p>The script was written to give them:</p>
<ul>
<li>A list of content that had been modified from a point in time until “now” (The time of execution).</li>
<li>Specify a specific space or recurse the child spaces for the named space.</li>
<li>return a specified XML structure</li>
</ul>
<p>In preparing for this post I’ve added some modifications/fixes:</p>
<ul>
<li>I’ve added two new return formats: JSON and HTML</li>
<li>fixed an issue that allows it to run on the 3.2.x release of Alfresco.</li>
</ul>
<p>Here are some insights to a few areas of the script:</p>
<p><strong>Lucene Date Queries</strong></p>
<p>There are two specific things to remember about Date queries:</p>
<p style="padding-left: 30px">
<strong>1 – Date Format.</strong> Date queries with Lucene are looking for the date in an I<a href="http://en.wikipedia.org/wiki/ISO_8601">SO8601</a> format using <a href="http://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations">combined date and time </a>in <a href="http://en.wikipedia.org/wiki/UTC">UTC</a>. (Even if you are looking just for the date it will complain if you don’t pass the time ).
</p>
<p style="padding-left: 30px">
To do this I had originally used the JavaScript Date prototype from <a href="http://delete.me.uk/2005/03/iso8601.html">http://delete.me.uk/2005/03/iso8601.html</a>. This works fine in pre 3.2 releases. But due to a <a href="https://issues.alfresco.com/jira/browse/ETHREEOH-2760">change in the 3.2 code line</a> top level objects are now sealed which means you can’t make these type of prototype change to Root level objects in the JavaScript libraries. So I’ve made simple modification to Date prototype code allowing me to pass a Date Object into the code which then does the coversion and returns a properly formated ISO8601 string.
</p>
<pre style="padding-left: 30px">function toISO8601String(date) { // based on http://delete.me.uk/2005/03/iso8601.html
/*
* accepted values for the format [1-6]: 1 Year: YYYY (eg 1997) 2 Year and
* month: YYYY-MM (eg 1997-07) 3 Complete date: YYYY-MM-DD (eg 1997-07-16) 4
* Complete date plus hours and minutes: YYYY-MM-DDThh:mmTZD (eg
* 1997-07-16T19:20+01:00) 5 Complete date plus hours, minutes and seconds:
* YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00) 6 Complete date
* plus hours, minutes, seconds and a decimal fraction of a second
* YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
*/
if (!format) {
var format = 6;
}
if (!offset) {
var offset = 'Z';
var date = date;
} else {
var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
offsetnum *= ((d[1] == '-') ? -1 : 1);
var date = new Date(Number(Number(date) + (offsetnum * 60000)));
}
var zeropad = function(num) {
return ((num < 10) ? '0' : '') + num; }
var str = "";
str += date.getUTCFullYear(); if (format > 1) {
str += "-" + zeropad(date.getUTCMonth() + 1);
}
if (format > 2) {
str += "-" + zeropad(date.getUTCDate());
}
if (format > 3) {
str += "T" + zeropad(date.getUTCHours()) + ":"
+ zeropad(date.getUTCMinutes());
}
if (format > 5) {
var secs = Number(date.getUTCSeconds() + "."
+ ((date.getUTCMilliseconds() < 100) ? '0' : '')</pre>
<pre style="padding-left: 30px">+ zeropad(date.getUTCMilliseconds()));</pre>
<pre style="padding-left: 30px">str += ":" + zeropad(secs);</pre>
<pre style="padding-left: 30px">} else if (format > 4) {
str += ":" + zeropad(date.getUTCSeconds());
}
if (format > 3) {
str += offset;
}
return str;
}</pre>
<p style="padding-left: 30px">
I’ve turned the original prototyping into a function, where I pass a Date object, with the date I want to work with, into this function. I also replaced any reference to ‘this’ with that data parameter. I will admit there is some excess code here that may never be called. But this was the quickest change to accomplish what I needed.
</p>
<p style="padding-left: 30px">
<strong>2 – Only dates are indexed.</strong> By default, only the date portion of the property is indexed. In general, most use cases only require the date . But in this case, the customer was interested in what may have changed over the period of an hour.
</p>
<p style="padding-left: 30px">
There are two options for this: Code around or modify how Alfresco indexes these DateTime properties. In this case I choose to code around it. This means that I am not modifying default behavior in Alfresco…always a plus.
</p>
<p style="padding-left: 60px">
<strong>Code Around.</strong> We have all the pieces we need to do this: We know the datetime we use for the start and end our range query. We also have access to the full datetime property (even if it wasn’t indexed).
</p>
<p style="padding-left: 60px">
I chose to pull this into a function that takes the collection (array) of documents found in our range query and then test to see if the datetime property of the node (in this case the modified datetime property) is between our the beginning and end of our range, if so then add it to our new filtered results array.
</p>
<pre style="padding-left: 60px">function filterResults(unfilteredResults) {
var now = new Date();
var filteredResults = new Array();
for each (node in unfilteredResults) {
var testDate = new Date(node.properties.modified);
//if the nodes modified date is between the passed date/time and now add
//to filteredResult Array
if ((filterDate <= testDate) && (testDate <= now)){
filteredResults.push(node);
}
}
return filteredResults;
}</pre>
<p style="padding-left: 60px">
This is clean and simple.
</p>
<p style="padding-left: 60px">
<strong>Modify Alfresco.</strong> While this sounds heavy, it actually isn’t. Our support team pointed me at <a href="http://forums.alfresco.com/en/viewtopic.php?f=4&t=9122">this forum post</a> in which <a href="http://twitter.com/andy_hind/">Andy Hind</a> from our engineering team shows us how to change the default behaviour:
</p>
<p style="padding-left: 60px">
In dataTypeAnalyzers.properties (located inside the war file in WEB-INF/classes/alfresco/model) change:
</p>
<p style="padding-left: 60px">
d_dictionary.datatype.d_datetime.analyzer=org.alfresco.repo.search.impl.lucene.analysis.DateAnalyser
</p>
<p style="padding-left: 60px">
to
</p>
<p style="padding-left: 60px">
d_dictionary.datatype.d_datetime.analyzer=org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser
</p>
<p style="padding-left: 60px">
Now rebuild your indexes. Depending on the amount of content in your repository it this may take some time. It may also not be possible to make this immediate because the SLA you have with your customers won’t permit it until you have scheduled maintenance downtime. (HA Clustering is helpful in these situations.)
</p>
<p><strong>System Folders</strong></p>
<p>One thing that may show up in these queries are system folders. These folders contain any of the rules that may be associated with a space. In most queries these folders and their content has no value, so we want to exclude them.</p>
<p>This string can be appended to your query string to exclude them:</p>
<pre style="padding-left: 30px">var systemFolder = "-TYPE:\"cm:systemfolder\" " +
"-TYPE:\"{http://www.alfresco.org/model/rule/1.0}rule\" " +
"-TYPE:\"{http://www.alfresco.org/model/action/1.0}compositeaction\" " +
"-TYPE:\"{http://www.alfresco.org/model/action/1.0}actioncondition\" " +
"-TYPE:\"{http://www.alfresco.org/model/action/1.0}action\" " +
"-TYPE:\"{http://www.alfresco.org/model/action/1.0}actionparameter\"";</pre>
<p><strong>PARENT vs PATH</strong></p>
<p>When we are performing queries for content within a space we have a couple of options. Two of the most common are PARENT and PATH.</p>
<p>PARENT queries work directly on a space. No recursion. It will return all of the nodes (spaces and content) in that space. In other words, PARENT queries work directly on a space without recursion. It will return all of the nodes (spaces and content) in that space alone, but will not return any nodes (spaces and content) that are in sub-spaces of that space.</p>
<p>PATH queries are a subset of XPATH. They are eagerly evaluated. Thus they can be memory (Caching) and CPU intensive. They are useful if you want more than what is just in the space you are working, may not know the location of the space, or if a space of that name may exist in multiple locations.</p>
<p>Be smart in your choice. Use PARENT as often as possible.</p>
<p><em>Note For Java extensions:<span style="font-style: normal"> u</span><span style="font-style: normal">nless the other clauses in your query are complex, it’s likely more efficient to enumerate (list / ls / dir) a space using the FileFolderService rather than a Lucene query. </span></em>ie. a simple query of the form “PARENT:[noderef]” would be better implemented using FileFolderService.list()</p>
<p><span style="font-style: normal">OK, enough about some of our design decisions let’s talk about how to use the web script.</span></p>
<p><span style="font-style: normal"><strong>Install</strong> </span></p>
<p><span style="font-style: normal">The web script is packaged in an amp file. It will work on Alfresco 3.1.1 and newer versions. (I tested up to Alfresco 3.2r) The <a href="http://code.google.com/p/alfresco-modified-content-list/downloads/detail?name=alfresco-updatedcontent-webscript.amp">alfresco-modified-content-list amp</a> and <a href="http://code.google.com/p/alfresco-modified-content-list/source/checkout">the source</a> can be found on the <a href="http://code.google.com/p/alfresco-modified-content-list/">google code project site</a>. Use the apply_amps script appropriate for your OS.</span></p>
<p><strong>How to use it</strong></p>
<p>The web script can be called using:</p>
<p style="padding-left: 30px">
http://localhost:8080/alfresco/service/updated/in/{path}/since?date=01/01/2010 13:10:10
</p>
<p style="padding-left: 30px">
http://localhost:8080/alfresco/service/updated/{path}/since?date=01/01/2010 13:10:10
</p>
<p>There is a slight difference in the two URLs: ‘in’</p>
<p>The ‘in’ allows you to tell the web script to just list the content in the space passed in the path parameter. Without the ‘in’ the web script will recurse the space structure from the passed path parameter down.</p>
<p>Next, the DateTime structure is not fixed: Possible (tested) formats are</p>
<p style="padding-left: 30px">
mm/dd/yyyy
</p>
<p style="padding-left: 30px">
yyyy/mm/dd
</p>
<p style="padding-left: 30px">
mm/dd/yyyy hh:mm:ss
</p>
<p style="padding-left: 30px">
yyyy/mm/dd hh:mm:ss
</p>
<p>The hh is expecting a 24 hour clock format. And millisecond and timezone tests are not supported</p>
<p>The query tests against the modified datetime property of the content. (Fairly easy to change it and test against the created property or any other custom metadata DataTime property.)</p>
<p>There are three return formats: XML (the default), JSON and HTML.</p>
<p style="padding-left: 30px">
<strong>XML</strong> The XML format is a simple structure that returns back the name of the file, the path to the file and the modification date. It is also the default format returned.
</p>
<pre style="padding-left: 30px"><contentItems>
<contentItem name='simple.doc' path='/Company Home/test/Portal' modDate='2010-04-26T16:21:20.612-06:00'/>
<contentItem name='simpledraft.docx' path='/Company Home/test/Portal' modDate='2010-04-26T16:28:44.569-06:00'/>
</contentItems></pre>
<p style="padding-left: 30px">
If there is no modified content a simple <span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;line-height: 18px;font-size: 12px"><contentItems /> </span>is returned.
</p>
<p style="padding-left: 30px">
<strong>JSON </strong>The JSON object is somewhat similar, a modified object containing a collection of node objects is returned
</p>
<pre style="padding-left: 30px">{ "modified": [
{ "node": {
"name": "test3.txt", "path": "some/path","modified": "2010-03-17T00:05:43.311-06:00"
}
},
{ "node": {
"name": "test2.txt", "path": "some/path","modified": "2010-03-17T15:07:30.301-06:00"
}
}
]
}</pre>
<p style="padding-left: 30px">
<strong>HTML</strong> This final format is more for testing than actual production use. It returns a simple html page with a list of modified items. It displays a simple unordered list, where each item is a csv list with key and values seperated by colons.
</p>
<p>In all of these return formats, the freemaker template processes the returned scriptNodes for a simple collection. This makes it easy to extend the return with the returned nodes properties to include things like nodeRef, creation date, node icons, download url, etc.</p>
<p>If you have suggestions for improvements or questions please feel free to comment here or on the google project. As always I’m willing to open access to the project to those will to help improve the code.</p>
<p><a href="https://jared.ottleys.net/alfresco/web-script-modified-content-list">Web Script: Modified Content List</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on May 17, 2010.</p>https://jared.ottleys.net/alfresco/searchd-how-do-i-enable-the-sharepoint-protocol2010-03-08T00:00:00-07:002010-03-08T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p><em>This is part of the Search’d series. Topics are taken from Search Engine keyword searches.</em></p>
<p>This second post is coming a bit late, but it is finally here. I’ve been getting ready for our World Wide Sales Kickoff. We will be kicking off our new fiscal year with great sessions for our worldwide group of <a href="http://www.alfresco.com/partners/">partners</a>. I’ll be presenting with two of my EMEA colleagues on Scaling Alfresco. I’ve chosen to tackle scaling our WCM product. It might even turn into some great information for a post or two here.</p>
<p>This weeks topic is one of the most popular searches that has lead to this blog: How do I enable the Sharepoint Protocol in Alfresco. At the outset let me tell you: I’m not going to explain how to actually configure the Sharepoint Services in this post, rather I want to point you to resource that take you through all of the steps of enabling the Sharepoint Protocol in Alfresco and how to use it. These are often over looked resources, but resources that can be really useful.</p>
<p>The first resource is the <a href="http://wiki.alfresco.com/w/images/6/62/Installing_and_Configuring_Alfresco_ECM_Community_Edition_3_2_r2.pdf">Installing and Configuring Alfresco Community Edition</a> guide found on the <a href="http://wiki.alfresco.com/wiki/Download_Community_Edition">Alfresco Community Download</a> page (or on the <a href="http://share.alfresco.com/share/page/site/community/dashboard">Alfresco Content Community Site</a> [registration required] in the <a href="http://share.alfresco.com/share/page/site/community/documentlibrary">Document Library</a> under Documentation in the Installation and Configuration folder for your installed release of Alfresco Community. This guide covers the basics of setting up and runningg Alfresco Community (It is also a preview of the documentation available as part of an <a href="http://www.alfresco.com/services/subscription/">Alfresco Enterprise Subscription</a> in the <a href="http://network.alfresco.com">Alfresco Network</a> site [<a href="http://www.alfresco.com/about/contact/">Alfresco Enterprise Subscription Required</a>].) You’ll find instructions for setting up and configuring Alfresco Sharepoint Services (on the server side) starting on the bottom of page 26.</p>
<p>The second resource covers using the Sharepoint Extension in Microsoft Office: <a href="http://share.alfresco.com/share/page/site/community/document-details?nodeRef=workspace://SpacesStore/c655e820-c926-4332-8ce0-9571fc239170">Managing Alfresco Content from within MS Office Community Edition</a>. (Requires Registration). This is also found in the <a href="http://share.alfresco.com/share/page/site/community/dashboard">Alfresco Content Community Site</a> [registration required] in the <a href="http://share.alfresco.com/share/page/site/community/documentlibrary">Document Library</a> but in the Tutorials folder.</p>
<p>These documents as well as others for using Share, Web Content Management, Document Management and Records Management can be found in the Content Community Site. You’ll also find presentations from past Community Conferences, Case Studies, White Papers and Webinars.</p>
<p><a href="https://jared.ottleys.net/alfresco/searchd-how-do-i-enable-the-sharepoint-protocol">Search’d: How do I enable the Sharepoint Protocol</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on March 08, 2010.</p>https://jared.ottleys.net/alfresco/searchd-how-do-i-disable-alfresco-share2010-02-26T00:00:00-07:002010-02-26T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>I’ve always found it fascinating what people searched for to find my blog. And more fascinating why my blog even came up in the results! The idea occurred to me that there were actually some meaningful questions in those searches. Yes, it was like one of those Windows 7 epiphanies. I’m going to start a new weekly (cross your fingers) series based on those search terms. New posts will come every Friday and will incorporate some facet of the search terms that brought people to this blog. The post size will vary based on the question and the technical depth it may require. Hopefully, this can prove to be valuable. I’ll also add a pitch here for <a href="http://www.alfresco.com/services/consulting/">Alfresco Professional Services</a> — bread on the table, mouths to feed and all — we offer the in-depth knowledge you need to help with your <a href="http://www.alfresco.com/services/subscription/">Alfresco Enterprise Subscription</a>. So without further ado….I give you: Search’d</p>
<p>Today’s search terms came in as “alfresco start up disable share studio”. For most Java Developers who have worked with Java application servers this will be pretty simple — you may even want to find something else to do now. For those new to Java or not even in the market to learn, it will not be. But the answer is pretty simple, but first some background…..</p>
<p>Java based applications can be packaged into several different formats. Typically you will find JARs and WARs. In some circumstances they are packaged in other formats as well…RARs and EARs being the most common. We at Alfresco like to use AMPs (which may or may not actually include Java code) to manager extensions to Alfresco. These archives are zip files with specific file layouts. Go ahead, take a copy of the alfresco.war file and open it with a archive manager that understands zip. It won’t hurt.</p>
<p>Now, if you downloaded Alfresco as one of the full blown installers or as a bundle with tomcat, go ahead and run the installer or unpackaged the archive. Once that is done navigate down into the tomcat/webapps directory. In there you will see a few directories and a couple of files with the extension .war (alfresco.war, share.war, mobile.war (Community only), studio.war (Community only)). To disable any of these simply remove the war file or rename it by anding a new/additional extension. I like to add the extension .off, which lets me clearly see that this part of Alfresco is — off. Removing the war files also may seem a bit brute force and unless you are really desperate for the space it won’t hurt anything to leave them. This is my preferred approach. If you ever want to add that part of Alfresco back, just remove the extension and restart Alfresco.</p>
<p>If you have already started Alfresco you will see directories that match the name of the war file. You will also need to remove those directories. (Don’t forget to restart Alfresco after removing those directories).</p>
<p>It’s as simple as that.</p>
<p>**Q: Is there ever a case where I would disable the Alfresco application? **Yes. If you are using Alfresco Share, you can add a bit of scale to your deployment by having Share installed on another server. It does require some additional configuration, but we won’t cover that today.</p>
<p>**Q: I just removed <strong>_\<em>_.war! Or I just removed the ___</em></strong> directory! How do I get it back? **For basic out of the box installs, stop Alfresco and simply grab the war file from the <a href="http://wiki.alfresco.com/wiki/Download_Community_Edition">Alfresco Community Page</a> or <a href="http://network.alfresco.com">Alfresco Network</a> (versions must match) and copy it back into the webapps directory. If you have added extensions via AMPs to your war files, reapply the amps to the new war file. Restart Alfresco. For the case where you deleted an expanded directory, with the war files in place, restart Alfresco and they will be deployed again.</p>
<p><a href="https://jared.ottleys.net/alfresco/searchd-how-do-i-disable-alfresco-share">Search’d: How do I disable Alfresco Share</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on February 26, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-pdf-toolkit2010-02-25T00:00:00-07:002010-02-25T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>A few weeks ago I made my first release of what I am calling the <a href="http://code.google.com/p/alfresco-pdf-toolkit/" target="_blank">Alfresco PDF Toolkit</a> on <a href="http://code.google.com" target="_blank">Google Code</a>.</p>
<p>Alfresco PDF Toolkit, or as I originally named it, <a href="http://svn.ottleys.net/public/alfresco/pdf-extension/" target="_blank">pdf-extension</a>, has been around for a while. It was originally hosted on <a href="http://svn.ottleys.net/public">my SVN server</a> and in then in <a href="http://svn.ottleys.net/alfresco" target="_blank">my Alfresco SVN Repo</a>. It was developed as a one-off side project at the request of an early Alfresco customer. The code has been sitting around with very few updates since that time. An occasional rebuild to make sure it would work with a new release of Alfresco but that was it. (I think it was original built for 2.1 or 2.2)</p>
<p><strong>Why the update?</strong> Focused attention. It is a way to get my hands dirty and adding some new features that have been rattling around in my head for a while now.</p>
<p>**What can it do? **This initial release has three functions:</p>
<ul>
<li>Split PDF — This option allows you to split a PDF every specified number of pages</li>
<li>Split at Page — This option allows you to split a PDF at a specified page</li>
<li>Merge PDF — This options allows you to append a PDF to another PDF</li>
</ul>
<p>All three options generate new PDFs leaving the originals untouched.</p>
<p>**Where can I find these actions? **The actions are available in the Run Action Wizard. They use the same names as above.</p>
<p><strong>What does the future hold?</strong> The current list of enhancements is as follows (bot not necessarily in this order):</p>
<ul>
<li>PDF Watermarks / Rubber stamping</li>
<li>Digital Signatures</li>
<li>TIFF to PDF transformation</li>
<li>Extract Pages</li>
<li>Delete Pages</li>
<li>Transform to PDF/A</li>
</ul>
<p><strong>Can I help?</strong> Yes! I’m more than happy to accept code contributions or add features the list of enhancements. And of course, merit can make you a contributor.</p>
<p>**Where can I find the code or the AMP again? **In the Google Code Project: <a href="http://code.google.com/p/alfresco-pdf-toolkit/">Alfresco PDF Toolkit</a></p>
<p><strong>*Note: **</strong>This is NOT an officially supported Alfresco Project. *</p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-pdf-toolkit">Alfresco PDF Toolkit</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on February 25, 2010.</p>https://jared.ottleys.net/alfresco/alfresco-wcm-forms-unique-identifier2010-02-24T00:00:00-07:002010-02-24T00:00:00-07:00Jared Ottleyhttps://jared.ottleys.netjared@ottleys.net<p>From time to time I’ve seen the need to provide a unique identifier in content created through the Alfresco WCM Forms Service. The reason why has varied from needing an unique identifier across renditions to needing a way to tie the content to User Generated Content (UGC) or some other database managed application. A simple solution is to leverage Java generated [Universally Unique Identifiers][1] (UUID) imported into the [Schema Definition of your form][2] via a [Java Backed Web Script][3]. **Note: **These are not the UUIDs used by Alfresco DM. UUIDs are not available/part of the AVM/WCM model used by Alfresco.</p>
<p>Java Backed Web Scripts provide a powerful way to include complex business process logic, enabling integrations through the [Alfresco Web Script framework][4] using a simple HTTP based API. In this case, we are using a simple Java class to generate a UUID and then returning that UUID wrapped in a XML Scheme Definition imported through a <xs:include> into a Schema Definition used to generate a form used to capture content in Alfresco’s WCM service.</xs:include></p>
<p>First let’s tackle the Java portion. This is a simple Java class that extends the Alfresco [DeclarativeWebScript][5] class. A Declarative Web Script allows you to mix Java classes, Freemarker templates (FTL) and Javascript. In this case we a want to leverage a FTL, which generates the XSD for the return. Here is the meat of our class:</p>
<p style="padding-left: 30px">
<code>public class UUIDWebScript extends DeclarativeWebScript<br />
{</code>
</p>
<p style="padding-left: 60px">
<code>@Override<br />
protected Map executeImpl(WebScriptRequest req,<br />
Status status, Cache cache)<br />
{</code>
</p>
<p style="padding-left: 90px">
<code> Map model = new HashMap();</code>
</p>
<p>`</p></p>
<p style="padding-left: 90px">UUID uuid = UUID.randomUUID();</p>
<p style="padding-left: 90px">model.put("uuid", uuid.toString());</p>
<p style="padding-left: 90px">return model;</p>
<p style="padding-left: 60px">}</p>
<p>`
<p style="padding-left: 30px">
<code>}</code>
</p>
Basically,
* Create a `map` of properties you want to return (by coding conventions this is called a model).
* Perform your business logic, in our case generate a UUID.
* Add the values you want to return to the template with a unique name to identify it in the template
* Return the map of properties
Next we want to format our return. This is done using a Freemarker Template . ([Freemarker][6] is a Java Based markup language.) Here is ours:
<p style="padding-left: 30px">
<code> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"<br />
xmlns:alfresco="http://www.alfresco.org/alfresco"<br />
elementFormDefault="qualified"></code>
</p>
<p style="padding-left: 60px">
<code><xs:complexType name="uuid"></code>
</p>
<p style="padding-left: 90px">
<code><xs:sequence></code>
</p>
<p style="padding-left: 120px">
<code> <xs:element name="uuid" fixed="${uuid}" type="xs:normalizedString"/></code>
</p>
<p style="padding-left: 90px">
<code> </xs:sequence></code>
</p>
<p style="padding-left: 60px">
<code> </xs:complexType></code>
</p>
<p style="padding-left: 30px">
<code> </code>
</p>
<p style="padding-left: 30px">
<code></xs:schema><br />
</code>
</p>
The FTL, as you can see , is a simple XSD. The value returned from our Java class, in the model, named uuid, is declared as ${uuid}.
Now let’s declare the web script so that it can be registered in Alfresco:
<p style="padding-left: 30px">
<code><webscript></code>
</p>
<p style="padding-left: 60px">
<code> <shortname>Generate UUID</shortname><br />
<description>Returns a UUID</description><br />
<url>/util/uuid</url><br />
<authentication>none</authentication><br />
<format default="xml">argument</format></code>
</p>
<p style="padding-left: 30px">
<code> </webscript></code>
</p>
And since we are using a Java class in our Web Script, we also need to declare it so that the Web Script framework can use it:
<p style="padding-left: 30px">
<code><?xml version="1.0" encoding="UTF-8"?><br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></code>
</p>
`</p>
<p style="padding-left: 60px"><bean id="webscript.org.alfresco.extension.uuid.uuid.get"<br />
class="org.alfresco.extension.uuid.UUIDWebScript"<br />
parent="webscript"><br />
</bean></p>
<p>`
<p style="padding-left: 30px">
<code></beans></code>
</p>
It is important to note here that the bean id for a Java backed Web Script requires a very specific format:
* Start by declaring this bean a webscript with: webscript.
* Next add the full Java package declaration: org.alfresco.extension.uuid
* Followed by the name used in your FTL and XML declaration with the method used to access the web script: uuid.get
* When packaged, the FTL and XML declaration must be in a folder structure that matches the Java package declaration.
This puts together all of the pieces on the Web Script side. It is easiest to manage this through an [AMP file][7], which we won’t discuss here how to create or install, but [code][8] and [amp file][9] are available at the project page: <http://code.google.com/p/alfresco-uuid-webscript/>
Once installed we can verify that it works by going to http://localhost:8080/alfresco/service/util/uuid in your browser (replace localhost and port as needed for your install). What you should see returned is an XSD that looks something like this:
<p style="padding-left: 30px">
<code><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"<br />
xmlns:alfresco="http://www.alfresco.org/alfresco"<br />
elementFormDefault="qualified"></code>
</p>
`</p>
<p style="padding-left: 60px"><xs:complexType name="uuid"></p>
<p style="padding-left: 90px"><xs:sequence></p>
<p style="padding-left: 120px"><xs:element name="uuid" fixed="9c2fce39-3351-47c9-bb05-4b002967a00d" type="xs:normalizedString" /></p>
<p style="padding-left: 90px"></xs:sequence></p>
<p style="padding-left: 60px"></xs:complexType></p>
<p>`
<p style="padding-left: 30px">
<code></xs:schema></code>
</p>
It is our template but with are declaration of ${uuid} replaced with aUUID: 9c2fce39-3351-47c9-bb05-4b002967a00d. With each call to this Web Script you will see a uniquely generated ID.
Now let’s integrate this with a simple XSD:
<p style="padding-left: 30px">
<code><?xml version="1.0" encoding="UTF-8"?><br />
<xs:schema<br />
xmlns:alf="http://www.alfresco.org"<br />
xmlns:xs="http://www.w3.org/2001/XMLSchema"<br />
xmlns:sample="http://www.alfresco.org/alfresco/sample"<br />
elementFormDefault="qualified"<br />
targetNamespace="http://www.alfresco.org/alfresco/sample"> </code>
</p>
<p style="padding-left: 30px">
<code><xs:include schemaLocation="webscript://util/uuid" /></code>
</p>
<p style="padding-left: 30px">
<code> </code>
</p>
<p style="padding-left: 30px">
<code><xs:element name="sample"></code>
</p>
<p style="padding-left: 30px">
<p>
<code></p>
<p style="padding-left: 30px"><xs:complexType></p>
<p style="padding-left: 60px"><xs:sequence></p>
<p style="padding-left: 90px"><xs:element name="sample" type="xs:normalizedString" minOccurs="1" maxOccurs="1"/><br />
<xs:element name="key" type="sample:uuid"/></p>
<p style="padding-left: 60px"></xs:sequence></p>
<p style="padding-left: 30px"></xs:complexType></p>
<p style="padding-left: 30px"></xs:element></p>
<p></code>
</p>
<p style="padding-left: 30px">
<code></xs:schema></code>
</p>
<p>
The important thing here is how we reference the Web Script, and you can do this with any custom Web Script designed to render XML Schema to be included in a form, by referencing the call with the declaration of webscript:
</p>
<p style="padding-left: 30px">
<code><xs:include schemaLocation="webscript://util/uuid" /></code>
</p>
<p>
Add this XSD to your Web Project and give it a run! In the form you will see the UUID, as a read-only field.
</p>
<p style="text-align: center">
<img class="size-full wp-image-299 aligncenter" src="http://jared.ottleys.net/files/2010/02/Picture-1.png" alt="UUID in Web Form" width="472" height="128" />
</p>
<p style="text-align: left">
Now in your XML output you will have a unique key that you can reference in your templates.
</p>
[1]: http://en.wikipedia.org/wiki/Universally_Unique_Identifier
[2]: http://wiki.alfresco.com/wiki/Forms_Authoring_Guide
[3]: http://wiki.alfresco.com/wiki/Web_Scripts#Java-Backed_Implementations
[4]: http://wiki.alfresco.com/wiki/Web_Scripts
[5]: http://dev.alfresco.com/resource/docs/java/web-client/org/alfresco/web/scripts/DeclarativeWebScript.html
[6]: http://freemarker.org/
[7]: http://wiki.alfresco.com/wiki/AMP_Files
[8]: http://code.google.com/p/alfresco-uuid-webscript/source/checkout
[9]: http://alfresco-uuid-webscript.googlecode.com/files/alfresco-uuid-webscript.amp
</p></p></xs:sequence></p></xs:complexType></p>
<p><a href="https://jared.ottleys.net/alfresco/alfresco-wcm-forms-unique-identifier">Alfresco WCM Forms: Unique Identifier</a> was originally published by Jared Ottley at <a href="https://jared.ottleys.net">I exist as I am</a> on February 24, 2010.</p>