Wednesday, September 19, 2018

How to access properties defined in alfresco-global.properties file without writing custom Java code


The properties in alfresco-global.properties file are only accessible from code within the Alfresco Repository classpath. These properties cannot be directly accessed from Share Web Scripts or YUI client code.

The examples on the Web show you how to access these properties using a custom repo Java bean but there is a way to access these properties without writing any custom Java code. You can access these properties using a Javascript backed repo Web Script.

In the JavaScript backed webscript first get the Spring root web application context. After getting the Spring root web application context get the instance of the “global-properties” bean from it. The “global-properties” bean is a java.util.Properties type so now the repo Web Script should be able to access all the properties from the alfresco-global.properties file.
























The Javascript backed repo Web Script can now expose or return these property values from alfresco-global.properties file. The repo Web Script returns the data as JSON.























Share Web Scripts and the YUI client can now access these properties defined in alfresco-global.properties file by calling the Javascript backed repo Web Script. 

Example of how the Share Web Scripts can consume the repo Web Script:

var url = "/api/formtek/edm/get-default-preview-option";
var result = remote.connect("alfresco").get(url);
if (result.status == 200)
{
     model.previewOptionValue = JSON.parse(result).defaultPreviewOptionValue;
}


Example of how the YUI client can consume the repo Web Script:

Alfresco.util.Ajax.jsonRequest(
{
       method: Alfresco.util.Ajax.GET,
       requestContentType: Alfresco.util.Ajax.JSON,
       url: Alfresco.constants.PROXY_URI + "api/formtek/edm/get-default-preview-option,
       successCallback: {
             fn: function (res) {

             },
             scope: this
        },
        failureCallback: {
             fn: function (res) {

             },
             scope: this
        }
});




Friday, August 17, 2018

How to Increase the Timeout for Generating the Alfresco PDF Preview


The Formtek EDM Module for Alfresco provides custom transformers to convert several CAD file formats (such as AutoCAD DWG) to PDF for previewing in Alfresco Share. The default time allowed for this transformation to complete is 120,000 milliseconds or 2 minutes. For most drawings, this is more than enough time to create the preview PDF. However, some complex drawings may need a little more time. You can change the timeout period on the transformer itself, but there are a few other timeout settings you'll to change as well to make it all work properly.

Here are the steps for increasing the time allowed for transforming DWG to PDF using the custom transformer provided by the Formek EDM Module. In this example, the time is being increased to 3.5 minutes (210,000 ms):

1) Add the following properties to the alfresco-global.properties file to change both the specific transformer timeout and the system-wide timeout and error time values:

# ==========================================================================
# DWG to PDF transformer timeout defaults to 180000 ms in Formtek EDM Module 
# 3.1.0.4 and 120000 ms in previous releases
# ==========================================================================
content.transformer.dwg2pdf1.extensions.dwg.pdf.timeoutMs=210000

# =======================================================
# System-wide timeout and error time default to 120000 ms
# =======================================================
content.transformer.default.timeoutMS=210000
content.transformer.default.errorTime=210000

Although these settings (after an Alfresco restart) do provide a DWG file more time to complete its transformation, the following odd behavior is observed:

  • If the transformation continues beyond 2 minutes, a second transformation attempt is surprisingly triggered. Sometimes, but not always, that second attempt is started before the first one finishes.
  • So now there may be two transformation processes running at the same time for the same file, and competing for same system resources until the first transformation completes.
  • Furthermore, the "busy" indicator in the Alfresco Share preview window does not go away until the second 2-minute period (or a total of 4 minutes) elapses, thus making you wait longer than necessary to see the PDF preview.
  • But wait. Instead of loading the newly generated PDF preview, the following message is displayed leading you to believe that the transformation attempt may have failed:
  • A manual window refresh does load the PDF preview (once the second transformation attempt completes), but that's a not-so-obvious detail.

Here's an example of the two transformation attempts for doc2.dwg when the logger property, org.alfresco.repo.content.transform.TransformerDebug, is set to DEBUG. In this example, the second attempts starts one second after the first one completes:

2018-08-14 09:38:39,171 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 11             dwg  pdf  doc2.dwg 2.5 MB -- pdf -- ContentService.transform(...)
2018-08-14 09:38:39,177 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 11             workspace://SpacesStore/28a8aa18-9681-417e-83ec-0fa8de0efbd1
2018-08-14 09:38:39,178 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 11             **a)  [50] dwg2pdf1<<Runtime>>           140,966 ms
2018-08-14 09:38:39,178 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 11.1           dwg  pdf  doc2.dwg 2.5 MB dwg2pdf1<<Runtime>>
2018-08-14 09:41:02,873 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 11             Finished in 143,702 ms

2018-08-14 09:41:03,455 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 12             dwg  pdf  doc2.dwg 2.5 MB -- pdf -- ContentService.transform(...)
2018-08-14 09:41:03,455 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 12             workspace://SpacesStore/28a8aa18-9681-417e-83ec-0fa8de0efbd1
2018-08-14 09:41:03,455 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 12             **a)  [50] dwg2pdf1<<Runtime>>           141,512 ms
2018-08-14 09:41:03,455 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 12.1           dwg  pdf  doc2.dwg 2.5 MB dwg2pdf1<<Runtime>>
2018-08-14 09:43:22,587 DEBUG [org.alfresco.repo.content.transform.TransformerDebug] [pool-13-thread-2] 12             Finished in 139,133 ms

Even though the first transformation attempt completed in about 2 minutes 23 seconds (143,702 ms), I don't see the PDF preview in Share for about 4 minutes 43 seconds (after the second attempt completes). 

To avoid this second transformation attempt, you need to change the read timeout for the HTTP socket connection, which defaults to 120,000 ms. So, in addition to Step 1 above, you'll also need to complete Step 2 as follows:

2) Create the tomcat\shared\classes\alfresco\web-extension\custom-slingshot-application-context.xml file with the following content, setting the readTimeout value accordingly (in my case, 210,000 ms).

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:context="http://www.springframework.org/schema/context" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
          http://www.springframework.org/schema/context 
          http://www.springframework.org/schema/context/spring-context-2.5.xsd"> 
  <bean id="connector.remoteclient" parent="connector.remoteclient.abstract" 
  class="org.alfresco.web.scripts.SlingshotRemoteClient" scope="prototype" > 
<!-- the http.connection.timeout value in milliseconds to apply to HTTP connections --> 
    <property name="connectTimeout"><value>10000</value></property> 
<!-- the http.socket.timeout value in milliseconds to apply to HTTP connections --> 
    <property name="readTimeout"><value>210000</value></property> 
  </bean> 
</beans>

3) Finally, restart Alfresco so the changes take effect.

Now, when you preview a newly upload drawing that takes more than 2 minutes to convert to PDF, only a single transformation attempt occurs and you will see the drawing preview sooner and without having to manually refresh the window.

Also note that this same configuration can be applied to other content transformers. However, you will need to configure the property that corresponds to that specific transformer in Step 1 above.

Tuesday, July 24, 2018

How to Remove Versioning for a Share Site (and Keep it Enabled Elsewhere)

Some use cases may require versioning to be disabled in some sections of your Alfresco repository while enabled in other areas. A use case that comes to mind involves a Share site where many documents are constantly being written while there is no requirement to hold on to older versions.

By default, Alfresco has versioning enabled. Alfresco uses the versionable aspect (cm:versionable) as a flag of sorts to assign to documents that will retain older versions of content as it is changed. As long as versioning is enabled, Alfresco will require that the versionable aspect be retained for any document on creation and on update. If a document has the versionable aspect, all new updates to the document will be versioned out of the box with 1.0+ for major versions and 0.1+ for minor versions. In order to ensure the document however does not increment by version, you'll need to make sure the versionable aspect is not assigned to the document.

In fact, it's very simple to create a Share rule that looks like this:





Here we create a rule that removes the versionable aspect as soon as the document is created. This is very simple to do and works on Share UI upload or if you use one of Alfresco's file servers to add the document. But, the real test for this process is when the document is updated with new content. When a document has its content updated Alfresco adds the versionable aspect back. In order to counteract this behavior, you have to create another rule that removes the versionable aspect on *update* like so:



At this point, whenever a document is updated via the "Upload New Version" action or via FTP or WebDAV, a new "version" is created but this document no longer retains its old versions. The old versions then become orphan nodes. At this point, when the Contentstore Cleaner job is run (out of the box this runs every morning at 4:00 am local time) the orphan nodes are then removed. All orphaned content at this point are then moved to the contentstore.deleted folder where they can either be safely removed from the file system or restored if needed.

Now, it's very likely that you may have pre-existing documents that will already have the versionable aspect added along with multiple versions at this point. If you would like to keep these from being versionable going forward and delete the old versions you can write a JS script like so:


// Set the name of the Share Site:
var siteName = "site1";

logger.log("This script removes versionable aspects for all documents in the " + siteName + " Share site.");

// Query used to return all documents (cm:content) from the documentLibrary folder and all subfolders:
var query = 'PATH:"/app:company_home/st:sites/cm:'+siteName+'/cm:documentLibrary//*" AND TYPE:\"cm:content\"';
logger.log("The query being used is: " + query);

// "documents" is our array of document results
var documents = search.luceneSearch(query);

logger.log("The number of documents found is: " + documents.length + ".");

// Iterate through the document list:
for (var i=0; i < documents.length; i++) {
  logger.log("Document name: " + documents[i].name);

  // If the document has the versionable aspect, then remove it:
  if (documents[i].hasAspect("cm:versionable")) {
    logger.log(" * " + documents[i].name + " has the versionable aspect. It will be removed.");
    documents[i].removeAspect("cm:versionable");
  } else {
    logger.log(" * " + documents[i].name + " does *not* have the versionable aspect.");
  }
}


This script should have the .js extension and placed in the /Data Dictionary/Scripts folder. It can be run once using a Share rule. Just make sure the mimetype is set to JavaScript in the advanced properties for the document details for the file.

This process will allow for you to keep certain parts of your repository from keeping unneeded versions and keep your file system from filling up.

Tuesday, June 19, 2018

Alfresco: How to Overwrite Node Created Date

The Alfresco cm:created property is a protected field.  It normally can't be changed.
For example, the following simple JavaScript code tries to change the created date property to January 31, 2012.  It actually runs with no error, but when you check for the value of the property on the node during the same transaction, it appears to be set.

But query the property again or look at the value in the Node Browser and you'll find that it hasn't changed.

var dir = "Sites/swsdp/documentLibrary/Presentations/Project Overview.ppt";
var node = companyhome.childByNamePath(dir);

// Change the created date
var jan312012 = new Date(2012, 0, 31);
node.properties["cm:created"] = jan312012;
node.save();

Sometimes it's desirable to be able to change the created date of the node.  This is especially useful when content is being migrated from another source into Alfresco and the creation date from the other system needs to be preserved in Alfresco.

Using the Alfresco Java API, it's possible to work around the problem.  To do that, we'll create the following Java method which can be called from Alfresco JavaScript. It will allow us to run any JavaScript method as user System.  It's a dangerous method to have around since it can run with no restriction, so it would be good practice  to package it separately and use it only for situations that warrant its use.

Create and compile the Java file com/formtek/example/util/RunAsSystem.java:

package com.formtek.example.util;

import org.alfresco.repo.jscript.BaseScopableProcessorExtension;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.mozilla.javascript.Context;
import org.alfresco.model.ContentModel;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;

import org.alfresco.repo.policy.BehaviourFilter;

public class RunAsSystem extends BaseScopableProcessorExtension 
{
    protected BehaviourFilter behaviourFilter;
    
    public void setBehaviourFilter(BehaviourFilter behaviourFilter)
    {
      this.behaviourFilter = behaviourFilter;
    }

    public void exec(final Function func) {
        
        final Context cx = Context.getCurrentContext();
        final Scriptable scope = getScope();
 
        RunAsWork<Object> raw = new RunAsWork<Object>() {
            public Object doWork() throws Exception {
  behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
                func.call(cx, scope, scope, new Object[] {});
                return null;
            }
        };
        AuthenticationUtil.runAs(raw, AuthenticationUtil.getSystemUserName());
    }   
}

Then wire this file into Alfresco with the file alfresco/extension/runas-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="com.formtek.RunAsSystem" parent="baseJavaScriptExtension" class="com.formtek.example.util.RunAsSystem">
      <property name="extensionName">
          <value>ftk</value>
      </property>
   <property name="behaviourFilter" ref="policyBehaviourFilter" />
    </bean>
</beans>

With that in place, it is possible then to write the following JavaScript:

function doit()
{
 // Find a test node
 var dir = "Sites/swsdp/documentLibrary/Presentations/Project Overview.ppt";
     var node = companyhome.childByNamePath(dir);
 // Change the created date
 var jan312009 = new Date(2009, 0, 31);
 node.properties["cm:created"] = jan312009;
 node.save();
}
ftk.exec(doit);

The method ftk.exec() will run any JavaScript method as user System.
In this example, it changes the creation dates on one of the standard sample documents that comes with Alfresco.

Wednesday, May 2, 2018

How to Connect to Alfresco with JConsole for JMX Access

The most straight-forward and basic way to make configuration changes in Alfresco is to stop Alfresco. Open alfresco-global.properties file (in tomcat/shared/classes sub-directory), make settings changes, save them and then start Alfresco. If you are making long-term changes to a production environment, this is the best way to go about doing that. However, before you can make these well-thought-out changes to production, it is a good idea to test them first in a test, staging or QA environment. But making changes to the configuration in global properties can be a bit time consuming. Were you aware that you can make most changes to Alfresco using JMX without a restart? Most Java applications do allow for this if they have enabled the JMX protocol.

The Java Management Extension (JMX) interface allows you to make changes to Alfresco Content Services through a JMX client that supports JMX Remoting (JSR-160). This client called JConsole allows you to:

  • Manage subsystems (includes many configuration setting changes)
  • Change log levels
  • Enable or disable file servers (FTP/CIFS)
  • Set the server to read-only mode
  • Set the server to single-user mode
  • Set server maximum user limit, including ability to prevent further logins
  • Count user sessions/tickets
  • User session/ticket invalidation

Setting up Alfresco for JMX connections

In order to allow for Alfresco to make use of JMX, we'll need to add a few settings to a Alfresco.

In alfresco-global.properties we add:

alfresco.jmx.connector.enabled=true
alfresco.rmi.services.port=50500
alfresco.rmi.services.host=<hostname>

For alfresco.rmi.services.host, make sure you are setting a hostname that is externally and internally resolvable. When you start Alfresco, you can make sure that JMX is enabled by running in Linux:

# sudo lsof -i :50500
COMMAND   PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
java    13248 root  552u  IPv6 13356213      0t0  TCP *:50500 (LISTEN)

You should of course, see a running process listening on 50500.

Running JConsole

If you happen to have GUI access on the server you are running Alfresco, you can run the following to start jconsole:

If you have a full JDK installed, you can typically run:

# jconsole

or

# java/bin/jconsole

If using Alfresco's JRE:

# java/bin/java -jar java/lib/jconsole.jar

If you can pull up jconsole, you can typically connect to Alfresco using the Local Process option. This won't require a long and complex URL or username/password. If you use the jconsole executable, you choose Local Process (make sure you select the running Tomcat process)



Next, you'll see a pop-up box showing insecure connection. Go ahead and click on the Insecure connection button.


Once it starts up, you'll see the start up window.



Click on the MBeans tab at the top and you should then see the Alfresco drop-down line.



If you have to use the Alfresco JRE, you will first need to add the JMX url and a username/password combo to get in.



You will need to add the following:


  • URL: service:jmx:rmi:///jndi/rmi://localhost:50500/alfresco/jmxrmi
  • Username: controlRole
  • Password: change_asap


Keep in mind that these are the default properties. It is possible that maybe an admin may have changed the username and passwords. In that case, you'll need to have a look through these files to get that information:


  • alfresco-jmxrmi.password
  • alfresco-jmxrmi.access


Keep in mind that this process will also allow you to connect to a remote Alfresco server provided you are on the same network.

Now, if your Alfresco install is in an AWS environment, you will need to follow a little bit of a different process. For reasons I don't fully understand, jconsole seems to have a difficult time making a direct connection to Alfresco running in AWS. What I've found that works is using an ssh tunnel to get to it.

To get this to work, you can run ssh client using a console on your workstation:

# ssh -D 1234 username@hostname

Next, you can run jconsole using a socket proxy:

# jconsole -J-DsocksProxyHost=localhost -J-DsocksProxyPort=1234

Note that this also works with jvisualvm:

# jvisualvm -J-DsocksProxyHost=localhost -J-DsocksProxyPort=1234

For jconsole, you then would fill in the rest of the URL, username and password in the dialog box and you should be able to get in.

service:jmx:rmi:///jndi/rmi://<hostname>:50500/alfresco/jmxrmi