Thursday, December 20, 2018

How to Set Up and Configure a Simple Alfresco Cluster

This article explains how to set up and configure a simple Alfresco cluster. This cluster will have these elements:


  • Alfresco installation on two nodes (each one will have its own Solr index)
  • Database install on one node
  • Contentstore being shared from one node
  • Proxy load balancer (using Apache)


Architecture

This can be spread out on to many different server nodes if you wanted to. But, we'll show you how to do this on 3 servers:


  • Alfresco install on alf1 node
  • Alfresco install on alf2 node
  • Database installation and shared NFS drive on db1 node


It is possible to do this on 2 servers (even 1 if you really wanted to) and have the database installed on one of the Alfresco servers but it's best for learning purposes to do it with 3 if you can. If you have enough resources locally, you can do this with VirtualBox or VMWare. For this article, I'll do it using AWS.

Logically speaking, an Alfresco cluster will have at least two Alfresco installations. Both installations will use a single database and will share a similarly configured connection. For my example, I am using MySQL. Both Alfresco installs will also use a single contentstore where the files are shared. But, each Solr will have to have its own indexes. Solr has index files that only pertain to its own local Solr install. Solr doesn't have the ability to use index files that could be shared among Solr installations.

I have built 3 virtual servers which are using Ubuntu 16.04 as OS:


  • alf1 - 2 cpu's with 8GB of memory (hard drive space doesn't need to be more than the default 8GB that you get with an AWS instance)
  • alf2 - 2 cpu's with 8GB of memory (same hard drive specs)
  • db1 - 1 cpu with 4GB of memory (same hard drive specs)


Prepare Servers

Once you've built these three server, log in on each one and run updates and upgrades to get each system up to speed:

  # sudo apt update && sudo apt upgrade

For the Alfresco servers make sure these ports are open in both AWS security group and in each server's firewall:


  • alf1 and alf2: 8080, 8443 and 80
  • db1: 3306, 2049 and 111


On alf1 and alf2 run this command to install the prerequisites for the Alfresco installation:

       # sudo apt install libfontconfig1 libfontconfig1-dev libice-dev libice6 libsm-dev libsm6 libxrender-dev libxrender1 libxext-dev libxext6 libxinerama-dev libxinerama1 libcups2 libcups2-dev libglu1-mesa libglu1-mesa-dev libcairo2 libcairo2-dev

Install MySQL

On db1 install MySQL server:

# sudo apt install mysql-server

The MySQL server install should give you the opportunity to set the root password. For this article, I have set the password with 'Alfr3sc0'.

Open the MySQL client:

# mysql -u root -pAlfr3sc0

In the MySQL client, issue these commands to create the Alfresco database and set the proper permissions:

mysql> create database alfresco;

   mysql> grant all privileges on alfresco.* to 'alfresco'@'alfresco' identified by 'Alfr3sc0';
Query OK, 0 rows affected, 1 warning (0.01 sec)

   mysql> grant all privileges on alfresco.* to 'alfresco'@'172.30.1.197' identified by 'Alfr3sc0';
Query OK, 0 rows affected, 1 warning (0.01 sec)

   mysql> grant all privileges on alfresco.* to 'alfresco'@'172.30.1.78' identified by 'Alfr3sc0';
Query OK, 0 rows affected, 1 warning (0.00 sec)

Install NFS

On db1 install the NFS server and set up the contentstore share:

   # sudo apt install nfs-kernel-server

Create the Alfresco contentstore shared folder:

# mkdir -p alfresco/contentstore

Add the following endpoints for each Alfresco server.

        # vi /etc/exports

    /alfresco/contentstore    172.30.1.78(rw,sync,no_root_squash,subtree_check)
/alfresco/contentstore    172.30.1.197(rw,sync,no_root_squash,subtree_check)

Restart the NFS Server so that these configurations are in effect:

    # service nfs-server restart

Issue the exportfs command to ensure that the correct shared folders are in effect:

    # sudo exportfs -ra


On both alf1 and alf2 install the NFS clients:

    # apt install nfs-common

Create the Alfresco Contentstore mount point folder:

    # mkdir /mnt/alfresco-contentstore
   
In the /etc/fstab file create a pointer to the

    # vi /etc/fstab

  172.30.1.198:/alfresco/contentstore  /mnt/alfresco-contentstore nfs rw,soft,intr,noatime,x-gvfs-show

Mount the mount points:

    # mount -a

Check the output of the df command to ensure the mount points are active:

    # df -kh

Test the mount points and make sure that your user can create a file there:

    # cd /mnt/alfresco-contentstore/
  # touch test
 

Install Alfresco

On alf1 and alf2 install Alfresco.

For this article, I have used Alfresco 5.2.4 but this should work with any recent version of either Alfresco Enterprise or Community:

# ./alfresco-installer.bin --mode text

Set the following configurations (make sure the ip addresses shown reflect the database server ip address in your environment):

JDBC URL: [jdbc:postgresql://localhost/alfresco]: jdbc:mysql://172.30.1.198/alfresco?useUnicode=yes&characterEncoding=UTF-8

JDBC Driver: [org.postgresql.Driver]: org.gjm.mm.mysql.Driver

Database name: [alfresco]: alfresco

Install the MySQL jdbc jar to your tomcat/lib directories:

# wget http://central.maven.org/maven2/mysql/mysql-connector-java/5.1.42/mysql-connector-java-5.1.42.jar

In alfresco-global.properties (in the tomcat/shared/classes folder) set the dir.contentstore to use the mounted Alfresco Contentstore drive:

dir.contentstore=/mnt/alfresco-contentstore

Start up alf1 and make sure it works as expected. To test, you should create a Share site, add a document and be able to successfully search for it. Follow these same steps for alf2 and test to see if you can find the same file there too -- in the expected folder and in search as well. With alf1 you can go ahead and add your license. The license info will be added to the database. Since both Alfresco installs are referencing the same database, the license info will be sufficient for both alf1 and alf2.

At a minimum, you should be able to log in to each Alfresco server. But, you will notice that if you log in on both alf1 and alf2 and then log out of alf1, you will still be logged in to alf2. This is because we are using multiple sessions for the same user which is not ultimately what we want. Go ahead and turn off both alf1 and alf2 Alfresco services. In the next section, we'll configure Hazelcast to enable session replication.


Share Cluster (session replication)

To configure Hazelcast for session replication you only need to remove the .sample from custom-slingshot-application-context.xml.sample file in tomcat/shared/classes/alfresco/web-extension folder so that it becomes custom-slingshot-application-context.xml.

Inside this file, change the 192.168.0.* to your internal ip address of your Alfresco server. After you do this for both Alfresco nodes, go ahead and restart both of them one at a time. Once you've done that, you can then log in to both Alfresco nodes.

Then, log out of alf1 and do a refresh of alf2. On alf2 you should be redirected to the login page. This demonstrates that you are using the same session on both nodes.

For this to be a true application cluster, we need to use only one hostname to access Alfresco. This hostname will then redirect to either Alfresco node based on a load-balancer's configuration. For the hostname we'll use alfrescodemo.com. In your workstation add the following to your hosts file (for demonstration purposes):

192.168.56.101  alfrescodemo.com

The ip address above should point to your database server (this is where we'll install Apache). We'll use Apache to handle this from the database server.


Apache Proxy/Load Balancing

On the db1 node install Apache 2.4 and mod_jk:

# sudo apt install apache2 libapache2-mod-jk

Open the default host configuration 000-default.conf in /etc/apache2/sites-available and add the following:

<VirtualHost *:80>

        ServerName alfresco.alfdemo.com
        ProxyRequests Off
        ProxyPassReverse /share balancer://app
        ProxyPass /share balancer://app stickysession=JSESSIONID|jsessionid nofailover=On
        <Proxy balancer://app>
                BalancerMember ajp://172.30.1.197:8009/share route=tomcat1
                BalancerMember ajp://172.30.1.78:8009/share route=tomcat2
        </Proxy>

</VirtualHost>

Save the file. In /etc/libapache2-mod-jk folder back up the workers.properties file and create a new one. In the new workers.properties file add the following (change the ip addresses to the ones you are using for alf1 and alf2 in your environment):

worker.list=loadbalancer

   worker.tomcat1.port=8009
   worker.tomcat1.host=172.30.1.197
   worker.tomcat1.type=ajp13
   worker.tomcat1.lbfactor=1

   worker.tomcat2.port=8009
   worker.tomcat2.host=172.30.1.197
   worker.tomcat2.type=ajp13
   worker.tomcat2.lbfactor=1

   worker.loadbalancer.type=lb
   worker.loadbalancer.balance_workers=tomcat1,tomcat2
   worker.loadbalancer.sticky_session=1

   worker.tomcat1.socket_keepalive=1
   worker.tomcat2.socket_keepalive=2
   worker.loadbalancer.method=B

Save this file and restart Apache by issuing:

# service apache2 restart

On the alf1 and alf2 servers, make the following changes to the server.xml (in tomcat/conf):

<Connector port="80" URIEncoding="UTF-8" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" maxHttpHeaderSize="32768" />

and change the following in the existing Engine stanza:

<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">

Use jvmRoute="tomcat1" for alf1 and jvmRoute="tomcat2" for alf2.

In alfresco-global.properties make these changes:

       alfresco.host=alfrescodemo.com
   alfresco.port=80
   share.host=alfrescodemo.com
   share.port=80

In share-custom-config.xml (in tomcat/shared/classes/web-extension folder) change all mentions of:

localhost:8080

to

localhost

Alfresco Cluster

Now, when you restart Alfresco you should be able to access http://alfrescodemo.com/share. This will take you to the Apache server installed on the database server. The loadbalancer functionality within Apache will then route you to either alf1 or alf2. At this point, you should have a functioning Alfresco cluster. This was a demonstration but you can see how simple this is to configure in a production environment. The best way to implement this is to go a step at a time and make sure that each component works as expected before adding the next components that will eventually make this is a fully functioning cluster.

Tuesday, November 13, 2018

Set Up and Configure FTPS with Alfresco


There are a number of ways to access Alfresco in addition to the common usage of the Share UI. One of the ways that Alfresco exposes itself is through FTP. Often however, organizations prefer to secure their FTP connection into Alfresco using certificates and keys (FTPS). This knowledge base article aims to show one how to configure Alfresco for FTPS access.

There are four areas to focus to accomplish this:

1. Alfresco Keystore and Truststore
2. FTP and FTPS configurations in Alfresco's global properties
3. FTP client connection instructions
4. Debugging in case you run into issues

First, we'll start with creating the keystore and cacerts file. Here we'll create a self-signed certificate for this KB articles' example.

Part 1. Create the keystore and cacerts file.


In this simple example, you will create a self-signed certificate for both Java keystore and cacert file.

Run the following command to create a key for Alfresco:

# <Alfresco Install Dir>/java/bin keytool -genkey -alias tomcat -keypass secret -keyalg RSA

You will be prompted for a password (use "secret" as shown from the command above). Also, give your name, the hostname, ogranization, city, state and country code.

This command will by default create a .keystore file in your user's home directory. In my example this will write to the .keystore file in /root/ directory (/root/.keystore).

Next, we'll export the generated key into a server.crt file

# <Alfresco Install Dir>/java/bin keytool -export -alias tomcat -keypass secret -file server.crt

After entering the password you will see the response:

Certificate stored in file <server.crt>

Now, import the server.crt into this Alfresco keystore:

# <Alfresco Install Dir>/bin keytool -import -file server.crt -keypass secret -keystore /root/.truststore

Provide the password (default "secret") and confirm the certificate. If all goes well, you will see this response:

Certificate was added to keystore


Part 2: Configure the alfresco-global.properties file


Open the file <Alfresco Install Dir>/tomcat/shared/classes/alfresco-global.properties and add the following settings:

### FTP Server Configuration ###
ftp.port=21 (Change to a different port if running Alfresco as a non-root user)
ftp.ipv6.enabled=false
ftp.keyStore=/root/.keystore
ftp.trustStore=/root/.truststore
ftp.keyStorePassphrase=secret
ftp.trustStorePassphrase=secret
ftp.requireSecureSession=true

Make sure the keyStore and trustStore paths are absolute or Java will not find the certificate at runtime.


Part 3: Connecting to Alfresco FTP server using FileZilla FTP client


On a client (Windows) open FileZilla and go to File > Site Manager. In the General tab, enter the Alfresco hostname and the ftp port number as it was set in Part 2.

Make sure these client configurations are used within Filezilla:

  • Protocol "FTP - File Transfer Protocol"
  • Encyption "Require explicit FTP over TLS"
  • Login Type "Normal"


Use your Alfresco user's username and password and then click Connect. You should see something similar in Filezilla that will confirm the FTPS connection to Alfresco is working:

Status: Resolving address of <alfresco_host>
Status: Connecting to 192.168.163.168:2121...
Status: Connection established, waiting for welcome message...
Status: Initializing TLS...
Status: Verifying certificate...
Status: TLS connection established.
Status: Logged in
Status: Retrieving directory listing...
Status: Directory listing of "/" successful

Keep in mind that during the first connection, Filezilla will prompt you to accept an unknown Certificate from Alfresco. Select "Always trust this certificate in future sessions". Subsequent connections will not show this dialogue. If there is any error connection, you should see something similar to the following in FileZilla:

Command: AUTH SSL
Response: 534 SSL/TLS sessions not available
Error: Critical error: Could not connect to server
Status: Disconnected from server

If you do see errors, double-check your Alfresco global properties file. If needed, have a look at Part 4 and follow steps to debug the connection. There should then be more information in Alfresco's log files.


Part 4: Configure and Enable DEBUG logging for Alfresco FTP


Add the following to <Alfresco Install Dir>/tomcat/webapps/alfresco/WEB-INF/classes/log4j.properties and restart Alfresco:

# File servers
log4j.logger.org.alfresco.fileserver=debug

# FTP server debugging
log4j.logger.org.alfresco.ftp.protocol=debug
log4j.logger.org.alfresco.ftp.server=debug


Monday, October 22, 2018

Creating an Ephesoft Batch Class for Export into Alfresco

This knowledge base article will show you how to set up an Ephesoft batch class that will export a batch document to an Alfresco server from Ephesoft.

1. First off, install and configure a 5.2.1 Enterprise version of Alfresco. Also, install Ephesoft 4.5.0. These can run on the same Windows server but be sure that either Ephesoft or Alfresco uses alternative ports as both make use of Tomcat. Out of the box, both use port 8080 for web consoles. Change one of them to use port 8081 for example.

2. In the Alfresco repository using Share, create a toplevel folder (I called our folder, "SimpleExport").

3. As the ephesoft user, log into Ephesoft.

4. In the Batch Class Management section, select the MailroomAutomationTemplate and click Copy. We'll use this template and extend it just enough to allow for an Alfresco export.
















5. Give the new batch class a name (I called mine, SimpleExport).

















6. Open the new batch class by double-clicking on it inside the list.









7. We'll need to add a new document type. In the left-hand corner, click on Document Type and click Add (and choose Add Locally). Simply adding it should be enough. For this example, we won't need index fields.

8. You can call it SimpleExportableDocument and use Simple Exportable Document Type as the description. Click Apply and Deploy.

9. Expand Modules > Page Process > RECOSTAR_HOCR and set Recostar_Deskew_Switch and Recostar_Font_Switch to On.



















10. Expand Modules > Export > CREATEMULTIPAGE_FILES and set Multipage File Export Process to ITEXT-SEARCHABLE and Searchable Output PDF to True


















11. Go to CMIS section of Export and fill in according to the screenshot. Afterwards, click on Apply and Deploy.






















12. Go to Upload Batch and select Select Files at the bottom.
















13. Select the uploaded batch, ensure the correct batch class is selected and click on Start Batch.


















14. Go to Batch Instance Management.














15. Go to Review/Validate.



























16. Click Review button.

17. Click Ok to confirm.















18. Wait for export to complete.














19. Check Alfresco to make sure this worked.



Thursday, October 4, 2018


Working with Alfresco 6.0 in Docker Containers


For the purpose of this knowledge base article, Alfresco 6.0 as part of a docker container was installed on Ubuntu 18.04. Below are links to instructions on how to install Docker and Docker-Componse. Should you need to install Docker and Docker-Compose for other operating systems, Docker's install section provides instructions for those as well. After docker and docker-compose are installed, you should be able to follow the rest with your own workstation no matter the OS.

Install Docker

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04

Install Docker-Compose

https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04

To download the needed docker-compose.yml file, you can request a 30-day trial here:

https://www.alfresco.com/platform/content-services-ecm/trial/download

Once you get the docker-compose.yml file, you can create a directory and place the file there.

First, make sure that the docker service is running:

# sudo service docker start

You then start up the containerized Alfresco using this command run in the same directory that contains docker-compose.yml:

$ sudo docker-compose up
Creating network "docker-compose_default" with the default driver
Creating docker-compose_libreoffice_1           ... done
Creating docker-compose_solr6_1                 ... done
Creating docker-compose_tika_1                  ... done
Creating docker-compose_alfresco-pdf-renderer_1 ... done
Creating docker-compose_postgres_1              ... done
Creating docker-compose_shared-file-store_1     ... done
Creating docker-compose_share_1                 ... done
Creating docker-compose_activemq_1              ... done
Creating docker-compose_alfresco_1              ... done
Creating docker-compose_imagemagick_1           ... done
Attaching to docker-compose_tika_1, docker-compose_postgres_1, docker-compose_alfresco-pdf-renderer_1, docker-compose_shared-file-store_1, docker-compose_solr6_1, docker-compose_share_1, docker-compose_libreoffice_1, docker-compose_imagemagick_1, docker-compose_alfresco_1, docker-compose_activemq_1

You will see Alfresco (and the other components) go through its startup messages.

Once you see a message similar to this in the logs:

alfresco_1 | 03-Oct-2018 21:28:47.888 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 232188 ms

You can then go to the UI in your browser:

http://localhost:8080/share

And log in with "admin" as the username and "admin" as the password.

If you ever need to have a deeper look into the Alfresco system itself as it's running in the containers, you can still directly access things like the Alfresco database or the filesystem where Alfresco resides.

To access the database, you can install a Postgresql client like PGAdmin3 and use the following connection endpoints:

Hostname: localhost
Port: 5432
Database: alfresco
Username: alfresco
Password: admin

To access the filesystem, you can use these commands to log into the container's shells (you should see something similar to the following):

$ docker ps 
CONTAINER ID        IMAGE                                            COMMAND                  CREATED             STATUS              PORTS                                                                                                                     NAMES
5528be5a1e5a        alfresco/alfresco-content-repository:6.1.0-EA1   "catalina.sh run -se…"   6 minutes ago       Up 6 minutes        0.0.0.0:8082->8080/tcp                                                                                                    docker-compose_alfresco_1
1716ad9c4e82        alfresco/alfresco-share:6.0                      "/usr/local/tomcat/s…"   6 minutes ago       Up 6 minutes        0.0.0.0:8080->8080/tcp                                                                                                    docker-compose_share_1
d6485df206f8        alfresco/alfresco-shared-file-store:0.1          "/bin/sh -c 'java -j…"   6 minutes ago       Up 6 minutes        0.0.0.0:8099->8099/tcp                                                                                                    docker-compose_shared-file-store_1
588003fbf981        webcenter/activemq:5.14.3                        "/app/run.sh"            6 minutes ago       Up 6 minutes        0.0.0.0:5672->5672/tcp, 0.0.0.0:8161->8161/tcp, 1883/tcp, 0.0.0.0:61613->61613/tcp, 61614/tcp, 0.0.0.0:61616->61616/tcp   docker-compose_activemq_1
58443c670b7c        alfresco/alfresco-pdf-renderer:1.3               "/bin/sh -c 'java $J…"   6 minutes ago       Up 6 minutes        0.0.0.0:8090->8090/tcp                                                                                                    docker-compose_alfresco-pdf-renderer_1
99582bc076ed        alfresco/alfresco-search-services:1.1.1          "/opt/alfresco-searc…"   6 minutes ago       Up 6 minutes        0.0.0.0:8083->8983/tcp                                                                                                    docker-compose_solr6_1
8bb3bb72d73c        postgres:10.1                                    "docker-entrypoint.s…"   6 minutes ago       Up 6 minutes        0.0.0.0:5432->5432/tcp                                                                                                    docker-compose_postgres_1
ec543faec7e8        alfresco/alfresco-libreoffice:1.3                "/bin/sh -c 'java $J…"   6 minutes ago       Up 6 minutes        0.0.0.0:8092->8090/tcp                                                                                                    docker-compose_libreoffice_1
e8073fe6a48f        alfresco/alfresco-tika:1.3                       "/bin/sh -c 'java -j…"   6 minutes ago       Up 6 minutes        0.0.0.0:8093->8090/tcp                                                                                                    docker-compose_tika_1
9d2bb4a7c605        alfresco/alfresco-imagemagick:1.3                "/bin/sh -c 'java $J…"   6 minutes ago       Up 6 minutes        0.0.0.0:8091->8090/tcp                                                                                                    docker-compose_imagemagick_1


Looking at the list we can see that the container id for the running Alfresco image is "5528be5a1e5a".

We can then use the docker interactive command to get access to the filesystem:

$ sudo docker exec -it 5528be5a1e5a /bin/bash

And now we have an interactive session. Below you can see that the login directory is in /usr/local/tomcat. As root however, you should be able to change to any other directory in this container if needed.

[root@5528be5a1e5a tomcat]# pwd

/usr/local/tomcat

If you ever need to make an edit to the running container's Alfresco settings (in alfresco-global.properties for example), you can do the following:

# vi shared/classes/alfresco-global.properties 

Just keep in mind that whatever changes you make will not persist should you turn off the container. To persist any changes to the system that will survive a restart, you'll need to come out of this interactive shell and run a docker commit command to save its state. To test this, you can do the following:

# touch test 

This will create an empty file called test in /usr/local/tomcat.

Next, commit the change by issuing the following command outside the container and on your local workstation:

# docker commit 5528be5a1e5a alfresco/alfresco-content-repository:6.1.0-EA1 

The format is:

# docker commit [container id] [image id]

After this, go ahead and run the local stop command to stop Alfresco.

$ sudo docker-compose down
Stopping docker-compose_alfresco_1              ... done
Stopping docker-compose_share_1                 ... done
Stopping docker-compose_shared-file-store_1     ... done
Stopping docker-compose_activemq_1              ... done
Stopping docker-compose_alfresco-pdf-renderer_1 ... done
Stopping docker-compose_solr6_1                 ... done
Stopping docker-compose_postgres_1              ... done
Stopping docker-compose_libreoffice_1           ... done
Stopping docker-compose_tika_1                  ... done
Stopping docker-compose_imagemagick_1           ... done
Removing docker-compose_alfresco_1              ... done
Removing docker-compose_share_1                 ... done
Removing docker-compose_shared-file-store_1     ... done
Removing docker-compose_activemq_1              ... done
Removing docker-compose_alfresco-pdf-renderer_1 ... done
Removing docker-compose_solr6_1                 ... done
Removing docker-compose_postgres_1              ... done
Removing docker-compose_libreoffice_1           ... done
Removing docker-compose_tika_1                  ... done
Removing docker-compose_imagemagick_1           ... done
Removing network docker-compose_default

Now, to see if our change persisted, go ahead and start the Alfresco containers:

$ sudo docker-compose up
Creating network "docker-compose_default" with the default driver
Creating docker-compose_tika_1                  ... done
Creating docker-compose_imagemagick_1           ... done
Creating docker-compose_libreoffice_1           ... done
Creating docker-compose_alfresco-pdf-renderer_1 ... done
Creating docker-compose_solr6_1                 ... done
Creating docker-compose_postgres_1              ... done
Creating docker-compose_activemq_1              ... done
Creating docker-compose_share_1                 ... done
Creating docker-compose_shared-file-store_1     ... done
Creating docker-compose_alfresco_1              ... done

...

Get the container id:

$ docker ps
CONTAINER ID        IMAGE                                            COMMAND                  CREATED             STATUS              PORTS                                                                                                                     NAMES
8275de822b40        alfresco/alfresco-content-repository:6.1.0-EA1   "catalina.sh run -se…"   3 minutes ago       Up 3 minutes        0.0.0.0:8082->8080/tcp                                                                                                    docker-compose_alfresco_1

Attach to the running container:

$ sudo docker exec -it 8275de822b40 /bin/bash

And if you run an "ls", you should see that your "test" file has persisted. Keep in mind that you can do the same with the database image if you don't want to have Alfresco reinstall its repository on every startup. Also, when you make commits, by default, older images are kept. So, if you need to, you can revert your current set of images back to the original ones.

Monday, October 1, 2018

Configuring Alfresco Behaviors for Associations

Alfresco metadata is stored as properties and also associations. Associations show relationships between source and target objects in the Alfresco repository.  When querying metadata via the API, or when viewing the metadata in the Node Browser, properties and associations are separated.

Even when defining an Alfresco behavior, there are differences in how a behavior is defined, depending on whether properties or associations are the source of the behavior action.

There are a number of tutorials or examples in blog articles that describe how to define a behavior in Alfresco, but most focus on behaviors that involve properties and not associations.  In this post, let's look at what is involved in creating a behavior triggered by the addition or deletion of an association.

First, we create a custom content model, called AspectTest.xml, as follows:

<?xml version="1.0" encoding="UTF-8"?>
<model xmlns="http://www.alfresco.org/model/dictionary/1.0" name="aspt:AspectTest">
    <description>Aspect Test</description>
    <imports>
        <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
        <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
    </imports>
    <namespaces>
        <namespace uri="aspecttest" prefix="aspt"/>
    </namespaces>
 
    <data-types/>
    <constraints/>
    <types/>
    <aspects>
        <aspect name="aspt:ATest">
            <title>ATest Aspect</title>
            <properties>
    <property name="aspt:projManager">
    <title>Project Manager</title>
    <type>d:text</type>
    <index enabled="true">
     <atomic>true</atomic>
     <stored>false</stored>
     <tokenised>both</tokenised>
     <facetable>true</facetable>
    </index>
    </property>
   </properties>
   <associations>
                <association name="aspt:projectManager">
                    <title>Project Manager</title>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>cm:person</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
     </target>
                </association>
   </associations>
            <overrides/>
            <mandatory-aspects/>
        </aspect>
    </aspects>
</model>

Put it into the alfresco/extension/models directory. This defines an association and a text field.
Then create the file to tell Alfresco to use the custom content model. Create the file testaspect-context.xml and place it in the alfresco/extension directory.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
 
<beans>
    <!-- Registration of new models --> 
    <bean id="testAspect.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
        <property name="models">
            <list>
                <value>alfresco/extension/model/AspectTest.xml</value>
            </list>
        </property>
    </bean>
 
    <bean id="onDeletePrjMgrProperty" class="org.alfresco.repo.policy.registration.AssociationPolicyRegistration" parent="policyRegistration">
        <property name="policyName">
            <value>{http://www.alfresco.org}onDeleteAssociation</value>
        </property>
        <property name="className">
            <value>{aspecttest}ATest</value>
        </property>
        <property name="associationType">
            <value>{aspecttest}projectManager</value>
        </property>
        <property name="behaviour">
            <bean class="org.alfresco.repo.jscript.ScriptBehaviour" parent="scriptBehaviour">
                <property name="location">
                    <bean class="org.alfresco.repo.jscript.ClasspathScriptLocation">
                        <constructor-arg>
                            <value>alfresco/scripts/onAssocUpdate.js</value>
                        </constructor-arg>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
 
    <bean id="onAddPrjMgrProperty" class="org.alfresco.repo.policy.registration.AssociationPolicyRegistration" parent="policyRegistration">
        <property name="policyName">
            <value>{http://www.alfresco.org}onCreateAssociation</value>
        </property>
        <property name="className">
            <value>{aspecttest}ATest</value>
        </property>
        <property name="associationType">
            <value>{aspecttest}projectManager</value>
        </property>
        <property name="behaviour">
            <bean class="org.alfresco.repo.jscript.ScriptBehaviour" parent="scriptBehaviour">
                <property name="location">
                    <bean class="org.alfresco.repo.jscript.ClasspathScriptLocation">
                        <constructor-arg>
                            <value>alfresco/scripts/onAssocUpdate.js</value>
                        </constructor-arg>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
          
</beans>

This file will wire in the custom model.  It also defines the behaviors. The last two bean definitions define behaviors that will run in Javascript for our newly defined association.  Note the reference to the Java class
org.alfresco.repo.policy.registration.AssociationPolicyRegistration.

The first of these invokes the behavior when associations are deleted, and the second is invoked when associations are added.
The behaviors call the Javascript function.  It is called alfresco/scripts/onAssocUpdate.js.

if (behaviour == null) 
{
    scriptFailed = true;
}

// Check the name of the behaviour
if (behaviour.name == null || behaviour.args[0].getType() != "{aspecttest}projectManager" || 
   (behaviour.name != "onCreateAssociation" && behaviour.name != "onDeleteAssociation") )
{
    scriptFailed = true;
} 
else 
{
 // Check the arguments
 if (behaviour.args == null || ('source' in behaviour.args[0]) == false || ('target' in behaviour.args[0]) == false) 
 {
  scriptFailed = true;
 } 
 else 
 {
  if (behaviour.args.length == 1) 
  {
   var source = behaviour.args[0].source;
   
   if(source!=null)
   {
    var assocs = behaviour.args[0].source.assocs["{aspecttest}projectManager"];
    var textVal = "";
    if(assocs!=null)
    {
     for(var i=0; i<assocs.length; i++)
     {
      if(i!=0) textVal += ",";
      textVal += assocs[i].properties["cm:userName"];
     }
    }
    source.properties["aspt:projManager"] = textVal;
    source.save();
   }
   else
   {
    scriptFailed = true;
   }
  } 
  else 
  {
   scriptFailed = true;
  }    
 }
}

The example Javascript code behavior will concatenate the list of users selected by the association into a text string that is populated into the new text property called aspt:projManager.

One of the peculiarities of Alfresco associations is that they are not searchable by queries.  As a workaround, this behavior will enable the search of user names selected by an association picker and stored the picked results in a text field.  The text field can be searched, even though the association cannot.

That's almost all.  But there is also some code involved in setting up the picker on an Alfresco search form.   For example, we can add the following extension configuration into Share to enable the assignment of our new association and also the display of it on a form.

<alfresco-config>
      <!-- Form configuration section - aspect -->
      <config condition="aspt:ATest" evaluator="aspect">
         <forms>
            <form>
               <field-visibility>
                  <show id="aspt:projectManager" />
               </field-visibility>
               <appearance>
                  <field id="aspt:projectManager">
                     <control template="/org/alfresco/components/form/controls/authority.ftl" />
                  </field>
               </appearance>
            </form>
         </forms>
      </config>
      <!-- Document Library config section -->
      <config evaluator="string-compare" condition="DocumentLibrary">
         <aspects>
            <visible>
               <aspect name="aspt:ATest" />
            </visible>
         </aspects>
      </config>
</alfresco-config>

The picker control for the association in Share will look like this:


And, using the above example picker results, user mjackson can be searched for as follows by searching on the text property which is synced with the values in the association (aspt:projManager:mjackson):