Monday, November 6, 2017

Why ImageMagick Fails in Alfresco 5.2.1 and How to Fix It

The Problem

If you are using Alfresco Content Services 5.2.1 on Windows, you may have noticed some image files fail to display their thumbnail and preview renditions in Alfresco Share. When you look at the ImageMagick configuration in the Alfresco Admin Console, you see that ImageMagick is disabled:


And, the following ImageMagick error is seen the alfresco.log file:

2017-10-24 11:07:20,320 ERROR [org.alfresco.repo.content.transform.magick.AbstractImageMagickContentTransformerWorker] [localhost-startStop-1] ImageMagickContentTransformerWorker not available: 09240020 Failed to perform ImageMagick transformation: 
Execution result: 
   os:         Windows Server 2012 R2
   command:    C:\Alfresco\5.2.1\imagemagick\convert.exe C:\Alfresco\52D9B8~1.1\tomcat\temp\Alfresco\ImageMagickContentTransformerWorker_init_source_137991397234068809.gif -strip -quiet C:\Alfresco\52D9B8~1.1\tomcat\temp\Alfresco\ImageMagickContentTransformerWorker_init_target_9151095455871890262.png
   succeeded:  false
   exit code:  1
   out:        
   err:        convert.exe: RegistryKeyLookupFailed `CoderModulesPath' @ error/module.c/GetMagickModulePath/670.
convert.exe: no decode delegate for this image format `GIF' @ error/constitute.c/ReadImage/509.
convert.exe: no images defined `C:\Alfresco\52D9B8~1.1
20

In the Alfresco 5.2.1 release, Alfresco replaced the previously used Ghostscript PDF interpreter with the new Alfresco PDF Render transformer (i.e., alfresco-pdf-renderer). This application is responsible for generating thumbnails and previews of various document formats. Unfortunately, the Windows version of this transformer conflicts with the ImageMagick transformer causing ImageMagick transformations to fail. The conflict occurs because two different Spring bean references were given the same name. As a result, the same parameters are being passed to both ImageMagick and the Alfresco PDF Render.

It's worth noting that this issue is fixed in the Alfresco 5.2.2 release, but if you are still using Alfresco 5.2.1, here's how you can fix it.

The Fix

You can resolve the transformer conflict by placing a copy of the custom-alfresco-pdf-renderer-transform-context.xml and custom-imagemagick-transform-context.xml files (contents shown below) in the tomcat/shared/classes/alfresco/extension folder and restarting Alfresco. Before you do the restart, ensure the img.gslib property does not exist (or is commented out) in the alfresco-global.properties file.

The Files

custom-alfresco-pdf-renderer-transform-context.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>

   <bean id="transformer.worker.subsys.alfresco-pdf-renderer" class="org.alfresco.repo.content.transform.pdfrenderer.AlfrescoPdfRendererContentTransformerWorker">
      <property name="mimetypeService">
         <ref bean="mimetypeService" />
      </property>
      <property name="executer">
         <bean name="transformer.alfresco-pdf-renderer.command" class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandsAndArguments">
               <map>
                  <entry key=".*">
                     <list>
                        <value>${alfresco-pdf-renderer.exe}</value>
                        <value>SPLIT:${options}</value>
                        <value>${source}</value>
                        <value>${target}</value>
                     </list>
                  </entry>
               </map>
            </property>
            <property name="processProperties" ref="#{systemProperties['os.name'].contains('Windows') ? 'transformer.worker.subsys.alfresco-pdf-renderer.processPropertiesWindows' : 'transformer.worker.subsys.alfresco-pdf-renderer.processPropertiesUnix'}" />
            <property name="defaultProperties">
               <props>
                  <prop key="options"></prop>
               </props>
            </property>
            <property name="errorCodes" >
               <value>1</value>
            </property>
         </bean>
      </property>
      <property name="checkCommand">
         <bean name="transformer.Pdfium.CheckCommand" class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandsAndArguments">
               <map>
                  <entry key=".*">
                     <list>
                        <value>${alfresco-pdf-renderer.exe}</value>
                        <value>--version</value>
                     </list>
                  </entry>
               </map>
            </property>
         </bean>
      </property>
   </bean>

   <bean id="transformer.worker.subsys.alfresco-pdf-renderer.processPropertiesWindows" class="org.springframework.beans.factory.config.MapFactoryBean">
      <property name="sourceMap"> 
         <map>
            <entry key="ALFRESCO-PDF-RENDERER_HOME">
               <value>${alfresco-pdf-renderer.root}</value>
            </entry>
         </map>
      </property>
   </bean>

   <bean id="transformer.worker.subsys.alfresco-pdf-renderer.processPropertiesUnix" class="org.springframework.beans.factory.config.MapFactoryBean">
      <property name="sourceMap">
         <map>
            <entry key="ALFRESCO-PDF-RENDERER_HOME">
               <value>${alfresco-pdf-renderer.root}</value>
            </entry>
         </map>
      </property>
   </bean>

</beans>

custom-imagemagick-transform-context.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>

   <bean id="transformer.worker.ImageMagick" class="org.alfresco.repo.content.transform.magick.ImageMagickContentTransformerWorker">
      <property name="mimetypeService">
         <ref bean="mimetypeService" />
      </property>
      <property name="executer">
         <bean name="transformer.ImageMagick.Command" class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandsAndArguments">
               <map>
                  <entry key=".*">
                     <list>
                        <value>${img.exe}</value>
                        <value>${source}</value>
                        <value>SPLIT:${options}</value>
                        <value>-strip</value>
                        <value>-quiet</value>
                        <value>${target}</value>
                     </list>
                  </entry>
               </map>
            </property>
            <property name="processProperties" ref="#{systemProperties['os.name'].contains('Windows') ? 'transformer.worker.ImageMagick.processPropertiesWindows' : 'transformer.worker.ImageMagick.processPropertiesUnix'}" />
            <property name="defaultProperties">
               <props>
                  <prop key="options"></prop>
               </props>
            </property>
            <property name="errorCodes" >
               <!-- The published error and fatal error codes are in the 400 and 700 ranges, but 1 is the most common and have seen 255 (could that by -1) -->
               <value>1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799</value>
            </property>
         </bean>
      </property>
      <property name="checkCommand">
         <bean name="transformer.ImageMagick.CheckCommand" class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandsAndArguments">
               <map>
                  <entry key=".*">
                     <list>
                        <value>${img.exe}</value>
                        <value>-version</value>
                     </list>
                  </entry>
               </map>
            </property>
         </bean>
      </property>
   </bean>

   <bean id="transformer.worker.ImageMagick.processPropertiesWindows" class="org.springframework.beans.factory.config.MapFactoryBean">
      <property name="sourceMap"> 
         <map>
            <entry key="MAGICK_HOME">
               <value>${img.root}</value>
            </entry>
            <entry key="MAGICK_CODER_MODULE_PATH">
               <value>${img.coders}</value>
            </entry>
            <entry key="MAGICK_CONFIGURE_PATH">
               <value>${img.config}</value>
            </entry>
            <entry key="DYLD_FALLBACK_LIBRARY_PATH">
               <value>${img.dyn}</value>
            </entry>
            <entry key="LD_LIBRARY_PATH">
               <value>${img.dyn}</value>
            </entry>
         </map>
      </property>
   </bean>

   <bean id="transformer.worker.ImageMagick.processPropertiesUnix" class="org.springframework.beans.factory.config.MapFactoryBean">
      <property name="sourceMap">
         <map>
            <entry key="MAGICK_HOME">
               <value>${img.root}</value>
            </entry>
            <entry key="DYLD_FALLBACK_LIBRARY_PATH">
               <value>${img.dyn}</value>
            </entry>
            <entry key="LD_LIBRARY_PATH">
               <value>${img.dyn}</value>
            </entry>
         </map>
      </property>
   </bean>

</beans>

Friday, October 27, 2017

Alfresco Web Scripts: Limiting Picker Search for Users

Both the Alfresco repository and Alfresco Share products extensively use Alfresco Web Scripts. Web Scripts are an important concept to understand in order to be able to customize Alfresco.

In this post we'll look at a requested customization for Alfresco Share and how it can be implemented by overriding the controller of an existing Alfresco Web Script.

First let's look at the following functionality in Alfresco Share.

By default, any user in Alfresco can start a workflow on a document which they have access to. If a user chooses to create a New Task (ad hoc task) workflow on a document, the workflow creation form is displayed, and on that form, the user is able to select a user to assign the new task to.

In the "Select..." dialog that pops up, the user is able to search across all registered Alfresco users.

It looks like this:


Customization Requirement


The requirement for the customization is, for all users other than admin users, to limit the search result to include only users that belong to the same groups which they are members of.

Implementation

How can we achieve that?

After a little investigation, we find that the user assignment popup is a kind of picker object, and that the client JavaScript code for the picker searches for users by making a call to a repository Web Script.  The controller for that Web Script is the following file:

alfresco/templates/webscripts/org/alfresco/repository/forms/pickerchildren.get.js.

[Note that this file can be found bundled in the repo jar file: alfresco-remote-api-5.x.x.jar]

Within the Web Script, the function that gets called to search for users is findUsers().  We'll change this method to limit the search results from the Picker to include only those users that belong to same groups that the user belongs to.








function findUsers(searchTerm, maxResults, results)
{
   var personRefs = people.getPeople(searchTerm, maxResults, "lastName", true);
   
   // create person object for each result
   for each(var personRef in personRefs)
   {
      if (logger.isLoggingEnabled())
         logger.log("found personRefs = " + personRefs);
      
      //filter out  the disabled users
      var daname = (search.findNode(personRef)).properties.userName;
 
      if(people.isAccountEnabled(daname)){
          results.push(
          {
              item: createPersonResult(search.findNode(personRef)),
              selectable: true
          });
      }
      else{
          results.push(
       {
           item: createPersonResult(search.findNode(personRef)),
           selectable: false
       });   
          if (logger.isLoggingEnabled())
              logger.log("User not added to results not enabled" + daname);
      }
   }
}




We see here that searches are currently being made using the Alfresco JavaScript API using root object 'people':

 var personRefs = people.getPeople(searchTerm, maxResults, "lastName", true);

The requirement for the customization is to change the search by limiting the results to only those users that belong to the same group as the current user.  We can again use the 'people' root-scoped object to find which groups the user belongs to:

var userGroups = people.getContainerGroups(person);

But the problem with the method getContainerGroups() is that it does not include system groups with the results.  It would be great if this method took another parameter as a flag to specify that system groups should be included in the result, but it doesn't. System groups include the Share groups created for each site for managing the four default Site roles: site manager, collaborator, contributor, and consumer.



If a user belongs to a Share site, we also want to include in the results any users which belong to any of the four Share site groups for that site. To find the additional site groups corresponding to the sites that a user belongs to, we will write a new method.  That is shown in the following code which takes a list and adds the Share site groups to the list.




function addSiteGroups(g)
{
    var s = siteService.findSites("", null, 0);
    for(var j=0; j<s.length; j++)
      {
 var perms = s[j].getSitePermissionGroups();
 for(var k=0;k<perms.length;k++)
 {
     g.push( groups.getGroupForFullAuthorityName(perms[k]).getGroupNode() );
 }
      }
  }

Here we query the site service to find all sites that the current user is able to access.  Then we get the names of the roles for the site -- there typically will be just the four we mentioned earlier. Then we get the group corresponding to each of those permissions and push the group nodes onto the list of groups:



groups.getGroupForFullAuthorityName(perms[k]).getGroupNode()

Once we have the list of groups that the user belongs to, we can make a query to find only those people that match the search criteria and also are members of those groups.

First we a build a string which contains part of the condition for the query that lists the groups that the user belongs:

 grpQuery = " AND (";
  
 // make concatenated list of groups
 for(var i=0; i<userGroups.length; i++)
   {
      if(i>0) 
      {
  grpQuery = grpQuery + " OR ";
      }
      grpQuery = grpQuery + "PARENT:\"" + userGroups[i].nodeRef.toString() + "\" ";
    }
    grpQuery = grpQuery + ")";



The query is as follows.  Note that 'filterTerm' corresponds to the text entered by the user within the UI. 'grpQuery' corresponds to the criteria for the group restriction. Note that we search for objects that are type cm:person and which do not have the cm:personDisabled flag applied.

var def = 
{ 
  query: "+TYPE:\"cm:person\" AND (-ASPECT:\"cm:personDisabled\") AND(cm:lastName:\"*" + filterTerm + "*\" OR cm:userName:\"*" + filterTerm + "*\")" + grpQuery, 
  store: "workspace://SpacesStore", 
    language: "fts-alfresco",
    page: paging
  }; 

Putting it all together, the combined changes to the Web Script are as follows:



function addSiteGroups(g)
{
 var s = siteService.findSites("", null, 0);
 for(var j=0; j<s.length; j++)
 {
  var perms = s[j].getSitePermissionGroups();
  for(var k=0;k<perms.length;k++)
  {
      g.push( groups.getGroupForFullAuthorityName(perms[k]).getGroupNode() );
  }
 }
}

// Modified by Formtek to find only group to which the user belongs
function findUsers(filterTerm, maxResults, results)
{  
 var paging = 
 { 
     maxItems: maxResults, 
     skipCount: 0 
 }; 
   
 var grpQuery = "";
 
 
 // If the user is not an admin, restrict the search
 if(!people.isAdmin(people.getPerson(person.properties["cm:userName"])))
 {
     // Find all the groups that the user is in
     var userGroups = people.getContainerGroups(person);
  addSiteGroups(userGroups);
  grpQuery = " AND (";
  
     // make concatenated list of groups
     for(var i=0; i<userGroups.length; i++)
     {
      if(i>0) 
      {
       grpQuery = grpQuery + " OR ";
      }
      grpQuery = grpQuery + "PARENT:\"" + userGroups[i].nodeRef.toString() + "\" ";
     }
     grpQuery = grpQuery + ")";
    }
 
 var def = 
 { 
   query: "+TYPE:\"cm:person\" AND -ASPECT:\"cm:personDisabled\" AND (cm:firstName:\"*" + filterTerm + "*\" OR cm:lastName:\"*" + filterTerm + "*\" OR cm:userName:\"*" + filterTerm + "*\")" + grpQuery, 
   store: "workspace://SpacesStore", 
   language: "fts-alfresco",
   page: paging
 }; 
 
 var personRefs = search.query(def); 
   
 // create person object for each result
 for each(var personRef in personRefs)
 {
    // add to results
    results.push(
    {
       item: createPersonResult(search.findNode(personRef.nodeRef)),
       selectable: true
    });
 }
}

To override the existing Web Script file, copy the original file into the extensions area and make the change to the findUsers() method as shown above here:

tomcat/shared/WEB-INF/classes/alfresco/extension/templates/webscripts/org/alfresco/repository/forms/pickerchildren.get.js.

One additional thing to notice is that filtering by group is only applied when the user is not an admin:

people.isAdmin(people.getPerson(person.properties["cm:userName"]))
 

Workflow assignments of tasks to users will now use the override method defined here and users will be filtered by group.

Wednesday, October 11, 2017

Auto-Complete in Ephesoft


Adding a semicolon separated list of values to an individual index can improve over-all accuracy while decreasing the processing time required to complete an Ephesoft batch. The auto-completion of the index field happens during the Validation process. A simple example is a field where the possible responses are ‘Positive’, ‘Negative’ or ‘No Response’. The person processing the form can simply enter a character or two that match the characters associated with the entries in the list of values. Once the corresponding value appear as the first entry in the list the user will strike the Down Arrow Key to populate the index with the selected value. A tab will take the person to the next field so they can continue to process the form.

Setup for an Auto-complete Field
First collect the values that are valid for a particular field. Now open the Doc Type in which the index value resides. Under the header ‘Field Option Values List’ enter the list of values. A semicolon is used to separate the list of values. See screenshot #1.

Screenshot #1:



Now look for the header label ‘Field Type’. Select the value ‘Combo’. See Screenshot #2. Apply your changes and you can test the results.

Screenshot #2:



Interacting With an Auto-complete Field 

Open a batch file in the Validation module that matches the Doc Type where you added your list of values. Tab down to the field where you entered your list of values. Once there enter a character or two that match the first few characters for the value you want applied to this field. Once you see your value appear at the top of the list simple hit your Down Arrow Key and this value will be used to populate this field. The tab key will take you to the next field. This simple change that can greatly decrease the processing time associated with a Doc Type within the Ephesoft application.

NOTE: The characters your entry are NOT case sensitive; a match will result even if the case of the characters do not a match. 

Thursday, August 10, 2017

Forming Relationships Between Documents in Alfresco




When building a deliverable from multiple documents or objects in Alfresco, how can you keep track of the source objects which were used to prepare this deliverable?

For example, let’s say you a preparing a proposal for a potential customer. It may be useful to build a bibliography of documents which contribute to your understanding of the customer’s needs and/or other information which is germane to the proposal preparation.  For instance, the proposal could have been developed based on meeting minutes, marketing collateral, a basis of estimate, regulatory constraints, etc., all of which are in the Alfresco repository.  The ability to associate these documents to the deliverable could prove to be an advantage to a workflow review of the proposal as well as a future set of references once the contract is awarded.  

This is just one example of the need to associate documents, there are many others.
Accomplishing this is fairly straight-forward with the Formtek Peer Association Extension for Alfresco.

Key Features of the Formtek Peer Association Extension
  • Allows a document or folder to be associated with other documents or folders
  • Available as an action
  • Compatible with any document type
  • Provides an indication that a document has one or more associations; or that a document is associated to one or more documents
  • Ability to download a document’s associations within a zip file

How it works

1.  From the Document Library or Document Details window, select the Manage Associations Action.  The Manage Associations Window will appear.

Interact via the Manage Associations Window

2.  Selecting “Add Association” will pop up a window allowing you to browse the repository to select folders and/or documents to associate to this document.

3.  Once your associations are added the Document Details Window will show the associations.

This document has associations to both a folder and a document
4.  Selecting the Manage Associations Action after associations have been added can be used to review the associations from and to the document, and provides the ability to remove one or more associations or to download all associations as a zip file.

This document has three associations and is associated with another document

For More Information

Our data sheet on all the Formtek Extensions to Alfresco, including Peer Association: 

And, a short (under nine minutes) YouTube video: