First Look at Alfresco Development Framework (ADF)

First Look at Alfresco Development Framework (ADF)

Alfresco development framework ADF

While looking for a replacement for an Alfresco Workdesk solution for our client at USDA, I heard from a co-worker that Alfresco had released their next generation UI Framework, aka Alfresco Development Framework (ADF), to replace their Share framework.

After reviewing the community documentation and list of components catalog, I determined that ADF could be a suitable replacement for our client. After doing a few prototypes and then refactoring ADF as the replacement UI solution for our customer, I learned the following, which may be useful for the Alfresco development community.

This blog describes how to get ADF running and then discuss the pros and cons of the product. Subsequent blogs will follow, which will further decompose and discuss other salient features of the framework and why it is a good alternative to Share.

Prerequisites

To use Alfresco Development Framework, you must meet the following requirements.

  • You need Alfresco 5.2 or above – since ADF is dependent on the Alfresco API specifications in Alfresco 5.2, the minimum requirement to use it is Alfresco 5.2.
  • If you want to use Alfresco Development Framework with both ECM (Alfresco) and BPM (Activiti) – you will need Activiti 1.5.1 or higher as well. ADF is fully integrated with both with no dependencies on having one or the other.
  • You also need to enable Cross-Origin Resource Sharing (CORS) on both Activiti BPM and Alfresco ECM

Why Alfresco Development Framework

why ADF

The first question that everyone asks is why ADF?  The most prominent answers are:

  • It uses up-to-date UI technologies – Alfresco Development Framework is based on the Angular JS framework. It is always up to date with the latest version of Angular. This provides an improved, fast and flexible single-page experience. We can easily incorporate, extend and customize different Angular components and services into the application easily.
  • It uses out-of-the-box components – The ADF team has already developed multiple components that can be used without any custom code. All the components are easily configurable and manageable. You can find a list of components
  • It is easy to connect to Alfresco Content Service (ACS) – As ADF is fully dependent on Alfresco, its use and integration with Alfresco is easy to configure. ADF leverages Alfresco’s REST APIs to communicate with the Alfresco content repository. These REST APIs are utilized using Alfresco-js-api.
  • It is easy to integrate with Alfresco Process Service (APS) – If you plan to use ADF only with Alfresco Process Service (APS, i.e., Activiti BPM) or with both ACS and APS, the configuration is still simple. Again, ADF uses Alfresco’s REST APIs to communicate with Activiti. For out-of-the-box ADF, the configuration can be done by simply changing one JSON file.
  • It is easy for developers to get started – There is a lot of documentation, guides, demos, and tutorials to get anyone up and running with Alfresco Development Framework, including those with little Angular experience. You can also use the demo shell (Alfresco Content App – fully developed app) to get started.
  • It is recommended over Share by Alfresco – Although Alfresco will continue to maintain Share, they recommend custom applications be developed using Alfresco Development Framework. In addition, customizing Share is cumbersome and complex compared to ADF. Unlike Share, ADF is the best solution for a simple and flexible application.

My Experience

When I first heard about Alfresco Development Framework, I was not too familiar with Angular, so I spent some time educating myself with the Angular JS 2 framework. It is quite different than version 1 of the framework, so anyone wishing to migrate an application from version 1.x to version 2.x, plan for a multi-month effort.

After my initial education and training period, things got easier. The ADF documentation and catalog gives some insight on how to get started working with ADF. For our project, we only needed to use ADF with (ACS). However, based on this experience, using it with APS or both should be nearly the same experience.

Major Alfresco Development Framework Components

components of ADF

Listed below are the major Alfresco Development Framework components that were used in our project and would most likely be used in other AD projects.  A brief description of these is provided, which will be followed by detailed examples in subsequent blogs.

Login Component – The main login page provides basic username-password authentication, using Alfresco REST APIs to authentic the user to the content repository. Alfresco ticket authentication is used across the application and the ticket is added to every request after the user’s initial login. The login page can be customized as-needed by adding or removing some of its properties.

Document List Component – The document list is used to access, view and manage folders and documents in the Alfresco content repository. The document list table view can also be easily customized to display custom and out-of-the-box properties in the table column.

Content Action Component  – This is used to add actions to a Document List. Content Actions can be used to perform many out-of-the-box repository actions like copy, move delete, download, etc. I also added custom actions by creating Angular components and executing them from the Document List.

Data Column Component – As mentioned above, this can be used to display the metadata of a folder or a document in the Document List and can be used to sort the columns and display custom properties.

Pagination Component – This component can be used to add pagination to a component. I used this in the Document List component to paginate the listed documents, and in the custom, search component to display paginated search results.

Tool Bar Component – I used this component as a container for headers, action, and titles.

Viewer Component – This is one of the major components that Alfresco ADF includes out-of-the-box. The viewer converts files to PDF for preview. This component can easily have added to the Document List.

Challenges

I faced some challenges on my first Alfresco Development Framework project.

  • ADF upgrade is frequent – This is a positive most of the time, but sometimes it caused instability in UI. During the upgrade, some of the CSS customization were overwritten and messed up. For instance, I started using ADF version 2.2 and then upgraded to version 2.3.  When I upgraded to version 2.4, the UI customizations were messed up. Therefore, I stayed with version 2.3.
  • Proxy settings and deployment – If you deploy the application in the same webserver container as Alfresco, there are no problems. But in my case, I deployed the ADF application to a separate webserver. To resolve this, I included the ADF application in a springboot project and used zuul proxy-to-proxy the requests to access Alfresco
  • SSO – Single sign on using external configuration was not easy to integrate. According to the ADF Team, this was implemented in Alfresco 6.0. After version 6, Alfresco added the ability for an external application to connect to Alfresco through single sign on. This was implemented using Keycloak and JWT tokens. If you are using Alfresco 5 with Kerberos, the default ADF configuration should work fine, but if you use any other external SSO with Alfresco 5, you will need to develop your own implementation from the Alfresco side and design a new login component on the ADF side.

Conclusion

When looking for a UI framework to integrate with and manipulate Alfresco or Activiti, ADF is the best choice. It provides a modern UI framework with many pre-built, out-of-the-box components. The steps to get started and integrate the framework are easy, which is another plus.

The Alfresco Content Application (ACA), the sample application shipped with ADF, can be used to get started. This sample application touches on most of the out-of-the-box features of ADF. I recommend starting with this application when developing with or customizing your ADF application.

I recommend ADF for anyone looking for a new user interface to work with Alfresco or Activiti. The challenges I face were resolved with some custom coding and twist. The ADF community is always available to help if there are any challenges during development and customization.

As we learn and implement this evolving technology into our internal and customer solutions, we will provide our lesson learned for the entire Alfresco ADF community.

Helpful links

  • Getting started page

* https://community.Alfresco.com/community/application-development-framework

* https://community.Alfresco.com/docs/DOC-7172-creating-your-application-with-adf

  • ADF (Ng2) – components

* https://github.com/Alfresco/Alfresco-ng2-components/blob/master/docs/README.md#adf-core

* https://Alfresco.github.io/adf-component-catalog/index.html

  • Supportive materials

* https://angelborroy.wordpress.com/2016/07/17/a-complete-vision-of-Alfresco-developer-framework/

* https://github.com/Alfresco/Alfresco-js-api#login

  • Videos

* Alfresco DevCon 2018: Play with ADF 2.0

* Tech Talk Live #100: Application Development Framework

  • Webinar

Get Started with Alfresco’s Application Development Framework (ADF)

Access Activiti Through an Apache Reverse Proxy with SSL

Access Activiti Through an Apache Reverse Proxy with SSL

Activiti is a light-weight workflow and Business Process Management (BPM) Platform. There seems to be sufficient documentation to get a copy up and running for older versions of this offering. Documentation for the newer versions could use a rewrite. After setting up the instance of Activiti (version 5.18.0) in one environment, the goal shifted to running it behind an Apache Reverse Proxy (RP) with SSL. A configuration file for the Apache RP was created like so.

# https://mycoolsite.com/activiti-explorer
 #####################################################
        ProxyPass /activiti-explorer https://10.10.10.10:8080/activiti-explorer
        ProxyPassReverse /activiti-explorer https://10.10.10.10:8080/activiti-explorer

Sparse support may be found for modifications that are needed for the server.xml file. That support plus using bits and pieces from other application installs resulted in a server.xml that looks like this.

    <Connector port="8080"
             maxThreads="150"
             minSpareThreads="25"
             connectionTimeout="20000"
             enableLookups="false"
             maxHttpHeaderSize="8192"
             protocol="HTTP/1.1"
             useBodyEncodingForURI="true"
             redirectPort="8443"
             acceptCount="100"
             scheme="https"
             secure="true"
             proxyName="mycoolsite.com"
             proxyPort="443"
             disableUploadTimeout="true"/>

The key to successfully implementing the SSL is the added line of secure=”true”. Without that one line, the site didn’t load properly.  This is a line that I hadn’t implemented in configuring other applications of a similar configuration. Hopefully, this little nugget will prove useful to your installation and configuration of Activiti.

Activiti


				
					
Spring Managed Alfresco Custom Activiti Java Delegates

Spring Managed Alfresco Custom Activiti Java Delegates

I recently needed to make a change to have Alfresco 4’s Activiti call an object managed by Spring instead of a class that is called during execution.  Couple of reasons for this:

  1. A new enhancement was necessary to access a custom database table, so I needed to inject a DAO bean into the Activiti serviceTask.
  2. Refactoring of the code base was needed.  Having Spring manage the java delegate service task versus instantiating new objects for each process execution is always a better way to go if the application is already Spring managed (which Alfresco is).
    • i.e. I needed access to the DAO bean and alfresco available spring beans.
    • NOTE:  You now have to make sure your class is thread safe though!

For a tutorial on Alfresco’s advanced workflows with Activiti, take a look at Jeff Pott’s tutorial here.  This blog will only discuss what was refactored to have Spring manage the Activiti engine java delegates.

I wanted to piggy-back off of the Activiti workflow engine that is already embedded in Alfresco 4, so decided not to define our own Activiti engine manually.  The Alfresco Summit 2013 had a great video tutorial, which helped immensely to refactor the “Old Method” to the “New Method”, described below.

Example:

For our example, we’ll use a simple activiti workflow that defines two service tasks, CherryJavaDelegate and ShoeJavaDelegate (The abstract AbstractCherryShoeDelegate is the parent).  The “Old Method” does NOT have spring managing the Activiti service task java delegates.  The “New Method” has spring manage and inject the Activiti service task java delegates, and also adds an enhancement for both service tasks to write to a database table.

Old Method

1. Notice that the cherryshoebpmn.xml example below is defining the serviceTask’s to use the “activiti:class” attribute; this will have activiti instantiate a new object for each process execution:

<process id="cherryshoeProcess" name="Cherry Shoe Process" isExecutable="true">
    ...
    <serviceTask id="cherryTask" name="Insert Cherry Task" activiti:class="com.cherryshoe.activiti.delegate.CherryJavaDelegate"></serviceTask>
    
    <serviceTask id="shoeTask" name="Insert Shoe Task" activiti:class="com.cherryshoe.activiti.delegate.ShoeJavaDelegate"></serviceTask>
    ...
</process>

2. Since we have multiple service tasks that need access to the same Activiti engine java delegate, we defined an abstract class that defined some of the functionality.  The specific concrete classes would provide/override any functionality not defined in the abstract class. 

...
import org.activiti.engine.delegate.JavaDelegate;
...
public abstract class AbstractCherryShoeDelegate implements JavaDelegate {
...
    @Override
    public void execute(DelegateExecution execution) throws Exception {
    ...
    }
...
}

public class CherryJavaDelegate extends AbstractCherryShoeDelegate {
...
...
}

New Method

Here’s a summary of all that had to happen to have Spring inject the java delegate Alfresco 4 custom Activiti service tasks (tested with Alfresco 4.1.5) and to write to database tables via injecting DAO beans.

  1. The abstract AbstractCherryShoeDelegate class extends Activiti engine’s BaseJavaDelegate
  2. There are class load order issues where custom spring beans will not get registered.  Set up depends-on relationship with the activitiBeanRegistry for the AbstractCherryShoeDelegate abstract parent
  3. The following must be kept intact:
    • In the Spring configuration file, 
      • Abstract AbstractCherryShoeDelegate class defines parent=”baseJavaDelegate” abstract=”true” depends-on=”ActivitiBeanRegistry”
      • For each concrete Java Delegate:
        • The concrete bean id MUST to match the class name, which in term matches the Activiti:delegateExpression on the bpmn20 configuration xml file 
          • NOTE: Reading this Alfresco forum looks like the activitiBeanRegistry registers the bean by classname, not by bean id, so likely this is not a requirement
        • The parent attribute MUST be defined as an attribute

Details Below:

1. Define spring beans for the abstract parent class AbstractCherryShoeDelegate and each concrete class that extends AbstractCherryShoeDelegate (i.e. CherryJavaDelegate and ShoeJavaDelegate). Have Spring manage the custom Activiti Java delegates where the concrete class.  The abstract parent must define it’s own parent as “baseJavaDelegate”, abstract=”true”, and depends-on=”ActivitiBeanRegistry”.

<bean id="AbstractCherryShoeDelegate" parent="baseJavaDelegate" abstract="true" depends-on="activitiBeanRegistry"></bean>
    
<bean id="CherryJavaDelegate"
class="com.cherryshoe.activiti.delegate.CherryJavaDelegate" parent="AbstractCherryShoeDelegate">
    <property name="cherryDao" ref="com.cherryshoe.database.dao.CherryDao"/>
</bean>

<bean id="ShoeJavaDelegate"
class="com.cherryshoe.activiti.delegate.ShoeJavaDelegate"  parent="AbstractCherryShoeDelegate">
    <property name="shoeDao" ref="com.cherryshoe.database.dao.ShoeDao"/>
</bean>

***NOTE: BELOW WILL NOT WORK

– Do NOT put any periods to denote package structure in the bean id!  Alfresco/Activiti got confused by the package “.”, where spring normally works fine with this construct.

– Also just because the concrete class is extending the parent abstract class, is not enough to make it work.

<bean id="com.cherryshoe.activiti.delegate.CherryJavaDelegate"
class="com.cherryshoe.activiti.delegate.CherryJavaDelegate" >
    <property name="cherryDao" ref="com.cherryshoe.database.dao.CherryDao"/>
</bean>

<bean id="com.cherryshoe.activiti.delegate.ShoeJavaDelegate"
class="com.cherryshoe.activiti.delegate.ShoeJavaDelegate" >
    <property name="shoeDao" ref="com.cherryshoe.database.dao.ShoeDao"/>
</bean>

2. Notice that the cherryshoebpmn.xml example below is using the “activiti:delegateExpression” attribute and referencing the Spring bean.  This means only one instance of that Java class is created for the serviceTask it is defined on, so the class must be implemented with thread-safety in mind:

<process id="cherryshoeProcess" name="Cherry Shoe Process" isExecutable="true">
    ...
    <serviceTask id="cherryTask" name="Insert Cherry Task" activiti:delegateExpression="${CherryJavaDelegate}"></serviceTask>

    <serviceTask id="shoeTask" name="Insert Shoe Task" activiti:delegateExpression="${ShoeJavaDelegate}"></serviceTask>
    ...
</process>

3.  The abstract class is now changed to extend the BaseJavaDelegate.  The specific concrete classes would provide / override any functionality not defined in the abstract class. 

...
import org.alfresco.repo.workflow.activiti.BaseJavaDelegate;
...
public abstract class AbstractCherryShoeDelegate extends BaseJavaDelegate {
...
    @Override
    public void execute(DelegateExecution execution) throws Exception {
    ...
    }
...
}

public class CherryJavaDelegate extends AbstractCherryShoeDelegate {
...
}

For more examples and ideas, I encourage you to explore the links provided throughout this blog. Also take a look at Activiti’s user guide, particularly the Java Service Task Implementation section. What questions do you have about this post? Let me know in the comments section below, and I will answer each one.

The blog Spring Managed Alfresco Custom Activiti Java Delegates was originally posted on cherryshoe.blogspot.com.

Mule and Activiti: Happy Together

Activiti out-of-the-box supports Mule tasks. When Activiti encounters a Mule task, it launches the configured Mule flow. This integration is exactly why my new project uses Mule in the first place. But Activiti also supports execution and task listeners: code that runs in response to an Activiti event. How can I cause Activiti to launch a Mule flow in response to such events?

The short answer is: write a generic Spring bean to launch a Mule flow; then use Activiti’s expression language to call this bean. Example snippet from an Activiti XML configuration file:

<sequenceFlow id='flow1' sourceRef='theStart' targetRef='writeReportTask'>  
    <extensionElements>
        <activiti:executionListener      expression="${cmMuleActivitiListener.sendMessage('vm://activiti-flow', 'Allman Bros. Band',
'bandResults', execution)}"/>
    </extensionElements>
</sequenceFlow>

 

This snippet causes Activiti to launch the vm://activiti-flow Mule flow when the Activiti workflow transitions from theStart to writeReportTask.Allman Bros. Band is the message payload, and the Mule flow results are placed in the bandResults Activiti flow variable. execution is a reference to the currently executing flow, and allows the Spring beancmMuleActivitiListener to give the Mule flow access to the Activiti flow variables.

The Spring configuration file is very simple:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="https://www.springframework.org/schema/beans"  
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <description>
        This Spring configuration file must be loaded into an
        application context which includes a bean named 
        "cm-mule-client".  This cm-mule-client bean must be an
        org.mule.module.client.MuleClient.
    </description>

    <!-- NOTE: bean ids to be used in Activiti expression language must be proper Java identifiers -->
    <bean id="cmMuleActivitiListener" class="com.armedia.acm.activiti.ActivitiMuleListener">
        <property name="realMuleClient" ref="cm-mule-client"/>
    </bean>
</beans>

Please note the description. The project must provide a Spring bean named cm-mule-client.

Finally, the primary method in the Spring Java class is also pretty straightforward:

 private void sendMessage(MuleClient mc, String url, Object payload, String resultVariable, DelegateExecution de)
            throws MuleException
    {

        Map<String, Object> messageProps = new HashMap<String, Object>();
        setPropertyUnlessNull(messageProps, "activityId",
          de.getCurrentActivityId());
        setPropertyUnlessNull(messageProps, "activityName",
          de.getCurrentActivityName());
        setPropertyUnlessNull(messageProps, "eventName",
          de.getEventName());
        setPropertyUnlessNull(messageProps, "id", de.getId());
        setPropertyUnlessNull(messageProps, "parentId",
          de.getParentId());
        setPropertyUnlessNull(messageProps, "processBusinessKey",
          de.getProcessBusinessKey());
        setPropertyUnlessNull(messageProps,
          "processDefinitionId", de.getProcessDefinitionId());
        setPropertyUnlessNull(messageProps,
          "processVariablesMap", de.getVariables());
        setPropertyUnlessNull(messageProps, "processInstanceId",
          de.getProcessInstanceId());

        MuleMessage mm = mc.send(url, payload, messageProps);

        log.debug("Mule sent back: " + mm.getPayload());

        if ( resultVariable != null )
        {
            de.setVariable(resultVariable, mm.getPayload());
        }
    }

    private void setPropertyUnlessNull(
      Map<String, Object> map, String key, Object value)
    {
        if ( value != null )
        {
            map.put(key, value);
        }
    }

 

This code sends the Mule message and waits for Mule to reply (since we call the Mule client send method). To post the message asynchronously we just need a corresponding method to call the Mule client dispatchmethod.

This small module allows me to configure Activiti to invoke Mule flows in response to Activiti events. And Activiti ships with the ability to invoke Mule flows as automated tasks. So now I have pervasive Mule support in Activiti: wherever Activiti supports executable code, it can call Mule. So happy together!

The Value of Workflow Based Reporting

Reporting is on every manager’s mind. “How can I provide the real-time facts about the progress of my department?,” “How do I get the funding I need?,” “How can I be sure changes in my organization are getting done correctly?” These are some of the questions we have answered with a click of a button!

Armedia has implemented a solution for a company requiring a robust reporting solutions for their complex business processes and strong needs for auditable solutions. The solution is easy to use and gives instant response to these requirements.

At INEOS Phenol there is a great need to report on their multi-faceted processes and workflows. When dealing with chemicals, changes to the equipment and environment can be a very involved process. This process, previously involving paper, was sometimes taking months to get through a single MOC (Management of Change). In such a large going concern, dealing with flammable chemicals, a single change can effect the entire system in many ways. This process involves many employees to investigate what actions they need to take on and how it will affect their department.

Implementing a strong electronic workflow process to manage the progress and activities, as well as instant reporting capabilities, was a challenge for us to configure. The solution must be able to instantly report on the workflow status, author, date, time and various action items that are needed to fully implement the change.

Two solutions were developed to make it easy for managers to quickly access this information without needing to physically run around the plant and various labs to monitor the hundreds of workflows in over eight departments.

INESO

 

One solution was establishing a workflow portal in their content management system, Alfresco. We created a dashlet that they can place on their home page and view immediately after logging on.  This “portlet” tracked the workflow number, the name, the author, the status and the last time it was updated. The status indicates the exact location/employee/department of the workflow in progress.

To open up a full report of all the workflows is just a click of a button at the top of the portal. In addition, if an authorized user needed a more in-depth report of a single workflow, they would just click the report icon to the right of each workflow.

Workflows_Report

 

The other solution was a portlet for the action items that initiated from the workflows in progress.  With proper permissions, users can see at-a-glance the name of the action item, assignee, originating workflow,  text of the action to be completed, status, and the target due date. In the same dashlet, a simple click opens up a report of an individual action item, all action items, or a filtered set of action items (i.e. all actions items with a “pending” status). This can give a manager full control of their area, instant knowledge of their departments’ workload and time to go get coffee!

Action_Portal

 

The return of investment on these solutions is untold hours of paper based processing, hours spent investigating where the bottlenecks are and grey hairs. Leveraging Alfresco dashlets with reporting capabilities and Activiti to run your complex workflows, can help your company get more done with less!