Published using Google Docs
Alfresco Maven SDK 2 Deep Dive
Updated automatically every 5 minutes

Table of Contents

Author: Martin Bergljung

Introduction

Preparing the Environment

Install Alfresco Community 5.0c

Install latest Maven 3.2.2

Setup environment variables

Customizing the Alfresco.war

Create a Maven Project for an Alfresco Repository JAR extension

Adding an Alfresco Repo Web Script to the Component A Repo module

Bringing in the Alfresco Maven SDK to build the Hello World Web Script Java controller

Unit Testing the Web Script Java Controller

Testing the Hello World Alfresco Repo Web Script from a Web Browser

Adding a Module to Build the Customized Alfresco Repo WAR

Running the Custom alfresco.war with the Tomcat 7 Maven Plugin

Update the Hello World Web Script and Watch Changes Take Effect Immediate

Integration Testing the Hello World Web Script

Tracking down what plugin version that is used and what dependencies that are included

Customizing the Share.war

Create a Maven Project for an Alfresco Share JAR extension

Adding an Aikau Page to the Component A Share JAR extension

Testing the Hello World Aikau Page from a Web Browser

Adding a Module to Build the Customized Alfresco Share WAR

Running the Custom share.war with the Tomcat 7 Maven Plugin

Update the Hello World Page and Watch Changes Take Effect Immediate

Adding Search

Adding Solr Search to the Embedded Tomcat

Introduction

If you have been using Maven for a while to manage your Alfresco extensions build you are probably wondering if it is ever going to get any better. Are you going to have to continue waiting forever for every build to complete, doing restarts for every change you make, having the integration tests take forever to complete, is this going to be the standard going forward?

Definitely not, there is a new Alfresco Maven SDK version 2 that changes all that. This new SDK together with Alfresco 5.0 will make it a pleasure developing Alfresco extensions. And for larger teams this SDK and Alfresco 5.0 will save a lot of time previously spent on building and restarts.

So what’s this new SDK about anyway? The first thing that you will notice is that JRebel, which is a proprietary class reloading system, has been substituted for Spring Loaded, an open source Java Agent for class reloading. The only thing that JRebel is better at is to automatically load any newly defined Spring beans, but other than that you are going to be fine with Spring Loaded. The second thing is the use of a new maven plugin called tomcat7-maven-plugin that makes use of so called virtual web applications. What it means is that maven can start an embedded Tomcat 7 instance and load a web application from your build project directories. This new approach is also completely IDE independent so whether you are using Eclipse, IDEA, or Netbeans, it should work fine.

Are you still using Ant..., you gotta let it go man! The Alfresco Development Kit is fully mavenized these days.

Preparing the Environment

There are some things that need to be done before we start hacking on our Maven projects.

Install Alfresco Community 5.0c

Follow standard package installer for your platform. You can get the installer from https://wiki.alfresco.com/wiki/Download_and_Install_Alfresco. 

You are probably thinking, why do I need to install Alfresco when the Tomcat plug-in is supposed to be able to run the complete Alfresco solution? I will show you how to deploy to it and run tests in the “real” environment.

Install latest Maven 3.2.2

Maven version 3.2.2 is required by the Alfresco Maven SDK version 2.0.0.

If you look in the Alfresco SDK Parent POM you will see something like:

<prerequisites><maven>3.2.2</maven></prerequisites>

Follow standard package installer for your platform. Or download and install as in the following example:

mbergljung@brutor:~/Downloads$ wget http://mirror.reverse.net/pub/apache/maven/maven-3/3.2.2/binaries/apache-maven-3.2.2-bin.tar.gz

mbergljung@brutor:~/Downloads$ tar -zxf apache-maven-3.2.2-bin.tar.gz

mbergljung@brutor:~/Downloads$ sudo cp -R apache-maven-3.2.2 /usr/local

mbergljung@brutor:~/Downloads$ sudo ln -s /usr/local/apache-maven-3.2.2/bin/mvn /usr/bin/mvn

Note. you might have to remove other previous maven installations first (and make sure M2_HOME is not set to some other installation).

After installation is finished you should see something like the following when printing the version of current maven installation:

mbergljung@brutor:~/Downloads$ mvn -version

Apache Maven 3.2.2 (45f7c06d68e745d05611f7fd14efb6594181933e; 2014-06-17T14:51:42+01:00)

Maven home: /usr/local/apache-maven-3.2.2

Java version: 1.7.0_60, vendor: Oracle Corporation

Java home: /usr/lib/jvm/java-7-oracle/jre

Default locale: en_GB, platform encoding: UTF-8

OS name: "linux", version: "3.13.0-40-generic", arch: "amd64", family: "unix"

Download Spring Loaded Java Agent JAR file

This is a Java agent (represented by a JAR file) that enables class reloading in a running JVM, you can get it from https://github.com/spring-projects/spring-loaded. It will enable you to update a Java file in your Alfresco extension project and then see the effect of the change directly in a running Alfresco-Tomcat-JVM instance, without having to re-build JARs, AMPs, and WARs and re-deploying them, saving you loads of time.

To use the Spring Loaded Java Agent you would do something like:

$ java -javaagent:<pathTo>/springloaded-1.2.1.RELEASE.jar -noverify SomeJavaClass

We will see next how to hook this Java agent to Maven.

Setup environment variables

It is necessary to give maven a bit of extra memory as it will run the complete Alfresco Tomcat application. If you want to you could even skip the first step of installing Alfresco 5, but we will keep a separate installation of Alfresco as it is quite common to have it when developing extensions.

In your environment configuration file, such as .bashrc, add:

# Maven Home

#

M2_HOME=/usr/local/apache-maven-3.2.2

# Java and Maven settings for running Alfresco Maven SDK 2.0

#

JAVA_HOME=/usr/lib/jvm/java-7-oracle

MAVEN_OPTS="-Xms256m -Xmx1G -XX:PermSize=500m -javaagent:/home/mbergljung/Downloads/springloaded-1.2.1.RELEASE.jar -noverify"

export M2_HOME JAVA_HOME MAVEN_OPTS

As we can see, this is where we setup the Spring Loaded Java Agent to be picked up by Maven via the MAVEN_OPTS variable.

Customizing the Alfresco.war

This section goes through how to build extension projects for the Alfresco.war web application (i.e. the Alfresco Repository application). We will look at how to set up a so called JAR extension, which will have one simple Web Script that we will use to demonstrate the new functionality that is achieved with the Maven Tomcat plugin and the Spring Loaded library. We will see how we can change the Java controller, JavaScript controller, and FreeMarker template and have those changes take immediate effect in a running Alfresco Repo web application. All this new functionality will shorten the development cycle significantly.

This section will also walk through how to do Unit Testing and Integration testing of the Web Script.

Create a Maven Project for an Alfresco Repository JAR extension

Use the Maven Quick Start artifact as basis: maven-archetype-quickstart

I like to start with a clean project that does not currently have any relationship to Alfresco, and then add stuff to make it an Alfresco build project. This makes it a lot easier to know what to-do when you want to make changes to the project, add new modules etc.

mbergljung@brutor:~/src$ mkdir alfresco-maven-sdk2-test

mbergljung@brutor:~/src$ cd alfresco-maven-sdk2-test/

mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn archetype:generate -DgroupId=com.mycompany.alfresco -DartifactId=component-a-repo-jar -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

We create the project with the intention of it becoming a component called A that will extend the Alfresco repository (Alfresco.WAR). The component project is created under a main directory called alfresco-maven-sdk2-test as we will use it as a parent project later on and create more sub projects as we go along.

This gives us a directory structure looking something like this:

Now, let’s update this structure so it becomes an Alfresco Repo extension JAR. Remove the Java class and its test class. Add a resources directory with module directory structure and module context and properties file. Also add an alfresco-global.properties file that will contain default values for properties specific to this component, these can be overridden in tomcat/shared/classes/alfresco-global.properties later on. Finally add a webscipts package as this module will contain a custom Alfresco Repository Web Script with Java backed controller. The webscript descriptor needs to be in a specific extension directory under the resource directory so add this directory structure too.

We now got this module structure:

The module.properties file looks like this:

module.id=mycompany-component-a

module.title=My Company - Component A

module.description=Component A does bla bla bla

module.version=1.0.0

module.repo.version.min=5.0

The module-context.xml will be picked up by Alfresco Repo’s spring context files search path and it looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>

    <import resource="classpath:alfresco/module/mycompany-component-a/context/webscripts-context.xml" />

</beans>

So as we can see, it brings in the webscripts-context.xml file too. It is good practice so split different Spring bean definitions into several files depending on what they represent, such as for example webscripts-context.xml that will contain all Spring Beans representing Java backed Web Script controllers. You can add more Spring bean config files like for example  scheduler-context.xml, actions-context.xml, activiti-context.xml, bootstrap-context.xml, patch-context.xml, policies-context.xml, service-context.xml etc to match your needs. This way it will be easier to maintain and find stuff when the project grows.

You can actually build this project right now:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-repo-jar$ mvn clean package

It will create the extension JAR as follows:

[INFO] Building jar: /home/mbergljung/src/alfresco-maven-sdk2-test/component-a-repo-jar/target/component-a-repo-jar-1.0-SNAPSHOT.jar

Maven assumes that it is handling JAR artifacts by default and if we look in the pom.xml file it is very basic:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany.alfresco</groupId>

  <artifactId>component-a-repo-jar</artifactId>

  <packaging>jar</packaging>

  <version>1.0-SNAPSHOT</version>

  <name>component-a-repo-jar</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

It just brings in JUnit so we can do some unit testing if we want to. Next step will be to actually build some Alfresco related stuff.

Adding an Alfresco Repo Web Script to the Component A Repo module

The idea here is to implement an example Web Script so that later we can show how the Java controller, JavaScript controller, and FreeMarker template can be updated during runtime.

Add the required files for a Hello World Web Script, the directory structure should now look as follows with the 4 extra files representing descriptor (hello-world.get.desc.xml), Java controller (HelloWorldWebScript.java), JS controller (hello-world.get.js), and template (hello-world.get.html.ftl):

The Web Script implementation is really simple and the files that are part of it have the following content:

hello-world.get.desc.xml:

<webscript>

    <shortname>Hello World</shortname>

    <description>Hello World demo web script</description>

    <url>/ecmstuff/helloworld</url>

    <format default="html">argument</format>

    <family>ECM Stuff Blog</family>

</webscript>

hello-world.get.html.ftl:

${hello} ${world}

hello-world.get.js:

model.world = "World!";

HelloWorldWebScript.java:

 

As we can see, my editor does not recognize the Alfresco related classes. We will fix this next by bringing in the Alfresco Maven SDK stuff. However, just before we do that we will register the Java backed controller for the Web Script in the webscripts-context.xml as follows:

 

You might have noticed that I have not explained any of the details around how to implement an Alfresco Repo Web Script. This is because there is already a ton of information about how to do that out there, and you probably know plenty about it already.

Bringing in the Alfresco Maven SDK to build the Hello World Web Script Java controller

We are going to bring in the Alfresco Maven SDK version 2 via our own Maven parent module (POM). This new parent module will have the Alfresco Maven SDK module (POM) as a parent and the Component A module (POM) as a sub-project.

Create a pom.xml file in the src/alfresco-maven-sdk2-test directory with the following content:

Here we are bringing in Alfresco Maven SDK version 2.0.0 via the parent element. For information about the latest Alfresco Maven SDK version have a look here: https://github.com/Alfresco/alfresco-sdk 

And here is information about what SDK version works with what Alfresco version: http://docs.alfresco.com/community/concepts/alfresco-sdk-compatibility.html 

Note also that from version 2.0 of the SDK the alfresco-sdk-parent artifact is available from the Maven Central repository (http://search.maven.org) and not from the Alfresco Artifacts repository (https://artifacts.alfresco.com). However, the rest of the artifacts, such as for example the alfresco-repository, are not available from Maven Central. So this practically means that you still got to put the following repository configuration in your Maven settings.xml:

<repository>

    <id>alfresco-public</id>

    <url>https://artifacts.alfresco.com/nexus/content/groups/public</url>

</repository>

<repository>

    <id>alfresco-public-snapshots</id>

    <url>https://artifacts.alfresco.com/nexus/content/groups/public-snapshots</url>

</repository>  

Further on, if you are building for an Enterprise Alfresco version you will have to configure access to the private Alfresco aritifacts repository available at: https://artifacts.alfresco.com/nexus/content/groups/private in your settings.xml file. And change the alfresco.groupId property value to org.alfresco.enterprise. For more info see: http://docs.alfresco.com/4.2/concepts/dev-extensions-maven-sdk-tutorials-alfresco-enterprise.html 

Next thing we do is to bring in the sub-project representing Component A. This is done via the modules element. After this we got a section with properties that we override. The default values for these properties can be found in the alfresco-sdk-parent artifact POM. The following is an example of some of the properties you will find in this POM:

You can for example see the properties related to the database configuration. If your project is using something else then the flat file database H2, such as MySQL, then you would know what properties to change. It is always good to use the database that will be used in production when developing, so you are not getting any nasty surprises later on when going live. However, the H2 database is very handy when you are running everything from maven and you want to start from a clean installation every time you run all your integration tests.

So we now got the parent POM sorted and it includes the POM for the Component A module. The parent POM is also bringing in all the library dependencies for the Alfresco platform distribution, but they are just telling us what version of each dependency we should use, and is not actually including the libraries (i.e. this is because the dependency is enclosed in the dependencyManagement tag).

Does this mean that the Java backed Web Script controller will compile now? Unfortunately not, we need to figure out what dependency that contains the org.springframework.extensions.webscripts classes and include it in the Component A module POM. One good way of doing this is to have a look at the alfresco-platform-distribution artifact POM for the available dependencies that we can use. To do this go to https://artifacts.alfresco.com  and search for alfresco-platform-distribution, you should see a result of this search looking something like this:

Now, click on the pom download link at the end of each row, pick the latest one. Opening the POM and you will find dependencies such as the following:

So to find the springframework related dependencies just do a search for it and you should see the following:

Now, which one of these dependencies should we include? We could put each one of them in the POM and see which one resolves the classes. Is there maybe a better way to find the dependency we are looking for? There is a way to find the exact library that the class is part of, using the class name search as follows:

This is when you might start to get a bit confused, the spring-webscripts artifact is not specified in the alfresco-platform-distribution POM. So you will not be able to build the Component A module by just adding the following to its POM:

    <dependency>

        <groupId>org.springframework.extensions.surf</groupId>

        <artifactId>spring-webscripts</artifactId>

    </dependency>

Maven will not know what version of the artifact we want to use as it is not part of the dependencyManagement section from the alfresco-platform-distribution POM. And it is best if we can use the dependencyManagement to control versions that are used for different artifacts, otherwise it is easy to forget to update the version of an artifact when you later on want to build the customization for a newer Alfresco version, possibly causing unexpected problems.

By opening up the POMs for the spring-surf and spring-surf-api we can see that the  spring-webscripts artifact is a dependency in the spring-surf artifact. So we can include the spring-surf artifact dependency as follows, without the version and scope elements (provided is picked up via the SDK ) specified, in the Component A Module POM to get everything to build:

Notice that we have also added a parent section to the POM to tell the Component A Module that it belongs under our Alfresco Maven Test parent module. We can now build everything with the following command:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn clean install

When building from now on it is important to use mvn install instead of mvn package as that will install all sub-modules in the local repository from where we can refer to them from for example a project that builds a WAR file, which we will do in a bit.

Unit Testing the Web Script Java Controller

Whenever we got some Java code we need to think about what unit tests we can add to make the system more stable and protect against regressions. In this case we can make sure somebody is not “accidentally” messing around with our Java controller by adding the following unit test:

package com.mycompany.alfresco.webscripts;

import org.junit.Test;

import org.mockito.Mockito;

import org.springframework.extensions.webscripts.*;

import java.util.Map;

import static org.junit.Assert.assertEquals;

import static org.junit.Assert.assertNotNull;

/**

 * Unit testing the Web Script Java Controller

 *

 * @author martin.bergljung@marversolutions.com

 * @version 1.0

 */

public class HelloWorldWebScriptControllerTest {

        @Test

        public void testController() {

            WebScriptRequest req = Mockito.mock(WebScriptRequest.class);

            Status status = Mockito.mock(Status.class);

            Cache cache = Mockito.mock(Cache.class);

            String helloPropName = "hello";

            String helloPropExpectedValue = "Hello";

            HelloWorldWebScript ws = new HelloWorldWebScript();

            Map<String, Object> model = ws.executeImpl(req, status, cache);

            assertNotNull("Response from Web Script Java Controller is null", model);

            assertEquals("Web Script Java Controller response is not " + helloPropExpectedValue,

                    helloPropExpectedValue, model.get(helloPropName));

        }

}

We don’t have a server running so we need to use Mockito to mock up some of the objects we need for the controller call. The test will make sure that the model variable hello always contains the value “Hello”.

Note. the test class has to be located in the same package as the web script implementation, otherwise we cannot call the protected executeImpl method.

To be able to use Mockito we need to add another dependency in the Component A Repo JAR POM:

           <!-- Mock objects during Unit testing, alfresco-parent-5.0.c.pom has dependencyManagement definition with version -->

            <dependency>

                <groupId>org.mockito</groupId>

                <artifactId>mockito-all</artifactId>

            </dependency>

This obviously does not test the Web Script completely but we will cover integration testing a bit later on in this article.

Testing the Hello World Alfresco Repo Web Script from a Web Browser

The component A Repo JAR is now built and contains the Hello World Web Script. To test it we can just drop the JAR file into the /alfresco/tomcat/webapps/alfresco/WEB-INF/lib directory of our Alfresco 5.0 Community installation and restart Alfresco Tomcat.

Check out the log while Tomcat is starting and you should see the Component A Module loaded as follows:

2014-12-17 12:44:50,400  INFO  [repo.module.ModuleServiceImpl] [localhost-startStop-1] Installing module 'mycompany-component-a' version 1.0.0.

Then use the http://localhost:8080/alfresco/service/ecmstuff/helloworld URL to try out the Hello World Web Script as follows:

OK, so far we have not really seen any new stuff, this is just the usual way of building Alfresco extensions with Maven, no groundbreaking improvements yet. To change the Web Script with current setup still requires a new build, update the JAR in the WEB-IN/lib, and a restart of Tomcat. Very time consuming. And we are not able to run any real integration tests, only basic unit tests and mockup tests.

In the coming sections we are going to take a look at how to run an embedded Alfresco Tomcat via Maven for speedier development lifecycle and for integration testing. The first thing we need to do though is add another sub-project that can be used to build the customized alfresco.war file.

Adding a Module to Build the Customized Alfresco Repo WAR

Before we setup our Maven build to be able to run an embedded Tomcat we have to somehow be able to provide Tomcat with the WARs to run. We are currently just producing one artifact, the Component A Repo Module, and it is a JAR file. Tomcat will want the alfresco.war file with the Component A Repo JAR extension integrated.

So the first thing we need to do is setup another submodule that produces the customized alfresco.war. Start by adding a new subdirectory called alfresco-war, with standard Maven directory structure and a POM, under the parent alfresco-maven-sdk2-test directory:

Besides the POM file there are two other files that are useful when running Alfresco:

In the alfresco-global.properties file we will setup where the content store, index and database resides by relying on properties setup in our top level project POM (i.e. alfersco-maven-test) or if not in our parent POM it will look for the properties in the Alfresco SDK Parent POM (i.e. alfresco-sdk-parent). It will also reconfigure some jobs that does not make sense to run in the default way when running locally via Maven.

Here is how the file looks like:

We also turn off protocols such as CIFS and FTP when running locally as you are most likely not going to use them and CIFS would for example not work any way with default ports being privileged under Linux and we are not running as root.

The dev-log4j.properties file will contain our custom classes such as for example:

Finally, the POM producing the Alfresco WAR has the following content:

The POM starts off by setting the packaging to war, as we want to create a customized alfresco.war, and then setting up the parent POM to the one we created. After that follows a number of dependencies:

To hook up the new alfresco-war artifact with our parent project we need to add it as a submodule in the parent POM. Otherwise it will not be built when we build the parent POM. Add it as follows:

At this point we can actually do a mvn clean install from the parent project and we should have the customized alfresco.war produced as follows:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn clean install

[INFO] Building war: /home/mbergljung/src/alfresco-maven-sdk2-test/alfresco-war/target/alfresco-war.war

However, we got a couple of problems here, first the name of the WAR file is alfresco-war.war, which is not what we want as it should be alfresco.war. This is because the artifact is called alfresco-war and we have not specifically configured any name for the WAR file so it uses the artifact name. To fix this we have to set the final name for the artifact as follows in the build section, we use a property to hold the value if we need it in more places later on:

The second problem is that the Alfresco SPP amp that we defined as a dependency, and that we want to be part of the alfresco.war, is not included as Maven knows nothing about AMP (Alfresco Module Packages) files by default. We need to include the maven-war-plugin in the build section and tell it to overlay the alfresco.war with the SPP AMP, this looks something like this:

Note that JAR dependencies does not have to be specified as overlays, they are automatically overlayed. OK, so we now got an alfresco.war with the Component A module and the standard SPP module. We still got no major improvement and have to stop Alfresco, replace the WAR, and then restart to see any updates to the Web Script. But we are prepared for running this WAR in an embedded Tomcat, which is next.

Running the Custom alfresco.war with the Tomcat 7 Maven Plugin

There are Maven plugins that can be used to run Apache Tomcat version 7 as part of the build and integration testing process. This is very useful as you can then run the Alfresco.war (the Alfresco Repository), Share.war (the Alfresco UI web application), and Solr.war (the search web application) and do real integration testing, or just test from the Share UI, without having a normal installation of Alfresco. This is also essential for a proper Continuous Integration (CI) setup.  

The tomcat7-maven-plugin will not be added to the alfresco-war artifact POM as you might have expected. This is because this plugin will need to be able to run more web applications than just the alfresco.war, later on it will have to also run the customized share.war and the solr.war to achieve a full Alfresco system.

So we need to create another submodule that will handle running the Alfresco Tomcat system embedded via Maven. Start by adding a new subdirectory called tomcat-runner, with standard Maven directory structure and a POM, under the parent alfresco-maven-sdk2-test directory:

Besides the POM file there are two other files:

Before we go into exactly what’s in these files we need to talk about Tomcat 7 and what is called a Virtual web application. During development it may be more productive to avoid copying files such as static resources, JSPs, classes, jars etc to the directory from where the Alfresco Tomcat installation is running (i.e. the one we did with the Alfresco 5.0 package installer in the beginning) and instead configure Tomcat to use files from their source locations in the build project. To do that, several customizations to the webapp context configuration are required:

Now let’s look at our virtual web application context configuration for the Alfresco repository webapp, the alfresco-webapp-context.xml file looks like this:

The context starts off by defining static resources, such as JS and CSS, for the alfresco.war to be picked up via the docBase definition. which is set to the alfresco-maven-sdk2-test/alfresco-war/target/alfresco directory. If we have any Alfresco repo modules that are implemented as AMPs, and contain static resources, then we would have to include the extra Resources paths to look something like this:

<Resources className="org.apache.naming.resources.VirtualDirContext"

extraResourcePaths="/=${project.parent.basedir}/component-b-repo-amp/target/component-b-repo-amp/web" />

The Loader configuration is setup to load any of the Component A Module’s classes from that subproject. Similar to the Resource configuration, if we have any Alfresco repo modules that are implemented as AMPs, and they got any classes, then we would have to update the virtual classpath to look something like this:

<Loader className="org.apache.catalina.loader.VirtualWebappLoader"

        searchVirtualFirst="true"

virtualClasspath="${project.parent.basedir}/component-a-repo-jar/target/classes;${project.parent.basedir}/component-a-repo-jar/target/test-classes;${project.parent.basedir}/component-b-repo-amp/target/classes;${project.parent.basedir}/component-b-repo-amp/target/test-classes;${project.parent.basedir}/component-b-repo-amp/target/component-b-repo-amp/config" />

Now let’s look at the simple HTML file called index.html, it has a link to the Alfresco repo web application and also links to the other web applications that we plan to make available later on:

The only thing we got left now to look at is the pom.xml itself, it starts out as usual with the standard POM artifactId, name, and parent definition, then it defines a new profile called run so you can control when you want to run the embedded Tomcat instance. The profile contains two plugins and the first one is the maven-resources-plugin that is used to copy the virtual webapp context to a location where Tomcat can pick it up and it also substitutes property values to make it ready for Tomcat:

The next plugin definition is the tomcat7-maven-plugin that is going to do the magic for us of running the Alfresco webapp embedded:

As we can see, the Tomcat 7 plugin will run before the integration test phase (i.e. pre-integration-test) so everything is ready when the integration tests kick off. The plugin has been configured so our Alfresco.war webapp will be deployed under the /alfresco context (so we can access it as usual via http://localhost:8080/alfresco). The webapp WAR content is picked up from the alfresco-war project that we created earlier. And the virtual webapp context configuration that we did earlier is also set up as context for the webapp.

Now, this is pretty much it for what we need to do, we should now be able to run an embedded Tomcat 7 installation with the Alfresco Repository web application deployed from the maven alfresco-war project. However, before that will actually work we need to somehow supply Alfresco with the create and upgrade scripts for the H2 database, as they are not available by default. The Alfresco SDK comes with a project called alfresco-rad, which includes all the necessary scripts, we just need to add a dependency for it in the alfresco-war project POM:

The dependency will only be active if we are running the embedded Tomcat instance via the run profile, we don’t need the H2 database stuff if we are packing the alfresco.war for a production deployment for example.

To hook up the new tomcat-runner artifact with our parent module we need to add it as a submodule in the parent POM, if your IDE is not doing it for you automatically. Otherwise it will not be built when we build the parent POM. Add it as follows:

Note. Before running the embedded Tomcat make sure the stand-alone Alfresco 5.0 Community installation is not running. If it is running you will get “Address already in use…” type of errors.

Now we can run Tomcat via Maven as follows by activating the run profile:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn clean install -Prun

When it starts up you should be able to see the following in standard out:

[INFO] --- tomcat7-maven-plugin:2.1:run (run-wars) @ tomcat-runner ---

[INFO] Running war on http://localhost:8080/

[INFO] Creating Tomcat server configuration at /home/mbergljung/src/alfresco-maven-sdk2-test/tomcat-runner/target/tomcat

[INFO] create webapp with contextPath:

[INFO] Deploying dependency wars

[INFO] Deploy warfile: /home/mbergljung/src/alfresco-maven-sdk2-test/alfresco-war/target/alfresco.war to contextPath: /alfresco

So we can see that it is running Tomcat on 8080 and deploying the Alfresco.war so it is available on http://localhost:8080/alfresco. Later on in the output we should see the Webapp connecting to the H2 database:

2014-12-29 16:34:55,178  INFO  [alfresco.repo.admin] [localhost-startStop-1] Using database URL 'jdbc:h2:./alf_data_dev/h2_data/alf_dev;MODE=PostgreSQL;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=FALSE;LOCK_MODE=0' with user 'alfresco'.

 2014-12-29 16:34:55,745  INFO  [alfresco.repo.admin] [localhost-startStop-1] Connected to database H2 version 1.4.182 (2014-10-17)

A bit after this you will see the following ERROR:

2014-12-27 21:27:43,853  ERROR [domain.schema.SchemaBootstrap] [localhost-startStop-1] Unable to validate database schema.

 java.lang.IllegalArgumentException: dbObject cannot be null.

I don’t know what it is and there is a bug filed for it at  https://github.com/Alfresco/alfresco-sdk/issues/245, Alfresco runs fine though, so it is not something that stops us from proceeding. Then we will have some other ERRORs indicating that Alfresco cannot find some of the tools it uses:

2014-12-27 21:27:52,387  ERROR [content.transform.RuntimeExecutableContentTransformerWorker] [localhost-startStop-1] Failed to start a runtime executable content transformer:

Execution result:

   os:         Linux

   command:    ./bin/pdf2swf -V

   succeeded:  false

   exit code:  1

   out:        

   err:        Cannot run program "./bin/pdf2swf": error=2, No such file or directory

 2014-12-27 21:27:52,409  ERROR [transform.magick.AbstractImageMagickContentTransformerWorker] [localhost-startStop-1] ImageMagickContentTransformerWorker not available: 11270001 Failed to perform ImageMagick transformation:

Execution result:

   os:         Linux

   command:    ./ImageMagick/bin/convert /tmp/Alfresco/ImageMagickContentTransformerWorker_init_source_8120671781002343751.gif /tmp/Alfresco/ImageMagickContentTransformerWorker_init_target_4089166879868743047.png

   succeeded:  false

   exit code:  410

   out:        

   err:        Cannot run program "./ImageMagick/bin/convert": error=2, No such file or directory

If you need the Flash preview to work and the image transformations for thumbnails etc, then you can update the alfresco-global.properties file in the alfresco-war project and point it to use these tools from the Alfresco 5.0c package installation you did in the beginning. If you don’t need these features for your customization then just move on.

Make sure the modules are installed:

2014-12-27 21:27:55,045  INFO  [repo.module.ModuleServiceImpl] [localhost-startStop-1] Installing module 'org.alfresco.module.vti' version 1.3.

 2014-12-27 21:27:55,057  INFO  [repo.module.ModuleServiceImpl] [localhost-startStop-1] Installing module 'mycompany-component-a' version 1.0.0.

Have a look so you are running the Alfresco version you think you are, and that correct amount of memory is allocated to the JVM, in the log a bit further ahead you should find something like:

 2014-12-28 15:14:34,273  INFO  [service.descriptor.DescriptorService] [localhost-startStop-1] Alfresco JVM - v1.7.0_60-b19; maximum heap size 910.500MB

 2014-12-28 15:14:34,274  INFO  [service.descriptor.DescriptorService] [localhost-startStop-1] Alfresco started (Community). Current version: 5.0.0 (c r91299-b145) schema 8,009. Originally installed version: 5.0.0 (c r91299-b145) schema 8,009.

 

Then wait for Tomcat to complete its deployment of the webapp and write the following to standard out:

INFO: WSSERVLET12: JAX-WS context listener initializing

Dec 27, 2014 9:28:09 PM com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>

INFO: WSSERVLET14: JAX-WS servlet initializing

Dec 27, 2014 9:28:09 PM org.apache.coyote.AbstractProtocol start

INFO: Starting ProtocolHandler ["http-bio-8080"]

We can now access Tomcat on http://localhost:8080, which will display our index.html page as follows:

Click on the Alfresco Repository link (the other links does not yet work as we have not deployed those webapps yet), you should see the following:

Note that the Alfresco Explorer UI has been removed from Alfresco 5, and this page is what you get instead.

Finally, try the custom Hello World web script and make sure it works:

A final note before we move on, you can run with another profile called purge to wipe out the /alf_data_dev directory to start clean. This removes any content changes and database changes (i.e. metadata updates). Here is how that looks like:

 mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn clean install -Ppurge,run

Note, the purge profile is part of the Alfresco SDK and can be found in the .../.m2/repository/org/alfresco/maven/alfresco-sdk-parent/2.0.0/alfresco-sdk-parent-2.0.0.pom file.

Update the Hello World Web Script and Watch Changes Take Effect Immediate

OK, we are finally at the point where we should be able to see the benefits from this setup. Let’s start with a change to the Java controller of the Web Script, change the text it returns as follows:

Then compile the change, you need to compile the Component A Module so you get a new class file for the HelloWorldWebScript class stored under component-a-repo-jar/target/classes. This class will then be reloaded into the JVM by the Spring Loaded agent (class files are watched for changes once a second by Spring Loaded):

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-repo-jar$ mvn compile

Now, refresh the Web Script page and you should immediately see the change take effect:

Pretty cool! Now change also what the JavaScript controller returns as follows:

This change also requires a compile to get the changed JavaScript controller file copied over to the component-a-repo-jar/target/classes directory. Further on, this change will not take effect without us refreshing the Web Scripts. Do this from the http://localhost:8080/alfresco/service/index page by clicking on the Refresh Web Scripts button at the bottom of the page.

Now refresh the Web Script page, you should see this change too now:

We have changed both the Java and JavaScript controller, now let’s also update the FreeMarker template and see how that works:

This change requires a compile as well to get the changed template file copied over to the component-a-repo-jar/target/classes directory. Now refresh the Web Script page, you should see this change now:

Note. changing the FreeMarker template does not require you to refresh all the Web Scripts from the http://localhost:8080/alfresco/service/index page, only a compile is needed.

So we can now start to see some significant improvements in the development process. Next up is to look at how the integration testing would work with the embedded Tomcat.

Note. any change to the Spring Bean for the Java controller will require a full restart of Tomcat. JRebel cannot handle this either, but it can reload new bean definitions.

Integration Testing the Hello World Web Script

The next thing we want to do is to make sure we have put in place proper regression testing of the Web Script. This is an absolute must to be able to have a stable and robust system, and to be able to do refactoring in the future without the system falling apart.

Note that Integration Testing (IT) is not the same thing as Unit Testing. Integration testing, as the name suggests, tests the whole solution integrated with the real database, services, and application server, and no mockup services are used. Integration tests run much slower than Unit Tests and is not something you want to run constantly during the development process, it should be run by the Continuous Integration (CI) job and before any commit. This means that

integration tests cannot be run by the same maven plugin, and in the same phase, as Unit Tests are executed.

Maven handles this by using different plugins that are executed in different phases in the Maven build lifecycle. If we look at the Maven Lifecycle phases and the test plugins that are used, it looks like this (note. not all phases have been included):

As we can see, trying to run an integration test via the surefire plugin would not work in our case as the Tomcat 7 instance has not been started yet. We need to actually use the failsafe plugin and activate it via, at least, the verify goal so all the integration test phases are executed, you can fail the build from the verify phase if you like, giving you a chance to first close any resources and stop any servers in the post-integration-test phase.

The Alfresco Maven SDK does not actually come with the maven-failsafe-plugin defined so we are going to have to add it to our project before we can do any integration testing. Because we are starting Tomcat before doing any integration testing we don’t want to do this for every submodule in our project. It might be a good idea to keep the integration tests in a separate submodule and define a profile in this module that brings in all the necessary plugins etc. As we have a multi-module project set up already it is easy to add another submodule, let’s call it integration-test-runner and after adding it our folder hierarchy now looks like this:

The POM defines the artifactId and the parent information as usual, don’t forget to add this module in the parent project if you are typing this in as we go along. The integration test class has also been added and is called HelloWorldWebScripIT, we will have a closer look at it later.

A new profile is defined and identified as enable-it (i.e. enable integration testing). This new profile will bring in any dependencies necessary for the integration test implementations and the plugins needed for running the tests. The profile also gives us the opportunity to control when we want the integration tests to be run.

The profile starts out like this:

The first thing in the profile after the id is a definition of a property that sets the report output encoding to UTF-8 so it can be used in the reports produced by the failsafe plugin. Then we bring in the HTTP Client library that will be used in the test implementation to make the REST call to the Web Script we are testing. The JUnit library is brought in as the failsafe plugin requires a test framework provider that will do the actual test execution.

The plugins are then defined starting with the maven-failsafe-plugin, which will run the integration tests in the integration-test phase and allow test results to be verified in the verify phase. We then want to make use of the virtual web application context definition we did earlier for the tomcat-runner project. To do that we bring in the maven-resources-plugin as follows:

Here we step up and into the tomcat-runner project contexts directory and grab the context files and filter them and then copy them to the output directory alfresco-maven-sdk2-test/integration-test-runner/contexts. The final plugin is the tomcat7-maven-plugin that we just need to point to the filtered and copied context files:

Tomcat 7 is started the same way as for the tomcat-runner profile in the pre-integration-test phase. We are however not starting it in the same JVM as Maven is running as we need to be able to run the tests in parallel, so a new JVM instance is forked and runs Tomcat. We need to do this otherwise we would just be stuck at the point where Alfresco Tomcat is started, and the execution would not continue running the integration tests. The Alfresco Repo WAR will pick up the context file that was just copied and filtered via the resource plugin.

What we need to do now is just to add an integration test class for the Web Script in the Component A module. The class need to follow a specific naming convention ("**/IT*.java", "**/*IT.java", and "**/*ITCase.java") for it to be picked up as an integration test.

To test the Web Script we will use the HTTPClient library to make the required HTTP GET call. Here is the test class:

package com.mycompany.alfresco.webscripts;

import org.apache.http.HttpEntity;

import org.apache.http.HttpResponse;

import org.apache.http.HttpStatus;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.impl.client.CloseableHttpClient;

import org.apache.http.impl.client.HttpClients;

import org.apache.http.util.EntityUtils;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

import static org.junit.Assert.assertNotNull;

/**

 * Integration Test (IT) for Hello World web script.

 *

 * @author martin.bergljung@marversolutions.com

 * @version 1.0

 */

public class HelloWorldWebScriptIT {

        @Test

        public void testWebScriptCall() throws Exception {

            String webscriptURL = "http://localhost:8080/alfresco/service/ecmstuff/helloworld";

            String expectedResponse = "Hello World";

            CloseableHttpClient httpclient = HttpClients.createDefault();

            try {

                HttpGet httpget = new HttpGet(webscriptURL);

                HttpResponse httpResponse = httpclient.execute(httpget);

                assertEquals("HTTP Response Status is not OK(200)", HttpStatus.SC_OK, httpResponse.getStatusLine().getStatusCode());

                HttpEntity entity = httpResponse.getEntity();

                assertNotNull("Response from Web Script is null", entity);

                assertEquals("Web Script response is not " + expectedResponse, expectedResponse, EntityUtils.toString(entity));

            } finally {

                httpclient.close();

            }

        }

}

Note that the integration tests are executed by JUnit 4 so we use the @Test annotation to tell JUnit to execute the test, if we don’t do this the test will not be run.

So this integration test class is quite simple, it starts by defining the URL for the Web Script and what response we expect when calling it. The HTTPClient library is then used to make the REST-based call. The response HTTP status code is checked and the response content is checked to make sure the Web Script is behaving the way we expect it to.

When we write integration tests it is just like coding normally, plus some asserts. If in this case we were building a desktop client or a web client that should use this Web Script, then we could actually write this integration test in the beginning in a kind of Test Driven Development (TDD) approach, stating how the client want to make the call and what it expects back.

To run the integration tests we kick off Maven as follows with the integration test profile enabled:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-repo-jar$ mvn clean install -Penable-it

Note that I am telling maven to run all the way to the install goal for all projects, this is so I’m sure the latest version of each sub-project is included in the alfresco.war etc. The log should look something like this:

[INFO] ------------------------------------------------------------------------

[INFO] Building Integration Test Runner 1.0-SNAPSHOT

[INFO] ------------------------------------------------------------------------

[INFO] --- maven-resources-plugin:2.7:copy-resources (copy-tomcat-resources) @ integration-test-runner ---

[INFO] Using 'UTF-8' encoding to copy filtered resources.

[INFO] Copying 1 resource

[INFO] --- tomcat7-maven-plugin:2.2:run (start-tomcat) @ integration-test-runner ---

[INFO] Running war on http://localhost:8080/

[INFO] Creating Tomcat server configuration at /home/mbergljung/src/alfresco-maven-sdk2-test/integration-test-runner/target/tomcat

[INFO] create webapp with contextPath:

[INFO] Deploying dependency wars

[INFO] Deploy warfile: /home/mbergljung/src/alfresco-maven-sdk2-test/alfresco-war/target/alfresco.war to contextPath: /alfresco

Jan 17, 2015 5:57:12 PM org.apache.coyote.AbstractProtocol start

INFO: Starting ProtocolHandler ["http-bio-8080"]

[INFO]

[INFO] --- maven-failsafe-plugin:2.18.1:integration-test (integration-test) @ integration-test-runner ---

[INFO] Failsafe report directory: /home/mbergljung/src/alfresco-maven-sdk2-test/integration-test-runner/target/failsafe-reports

[INFO] Using configured provider org.apache.maven.surefire.junitcore.JUnitCoreProvider

[INFO] parallel='none', perCoreThreadCount=true, threadCount=0, useUnlimitedThreads=false, threadCountSuites=0, threadCountClasses=0, threadCountMethods=0, parallelOptimized=true

-------------------------------------------------------

 T E S T S

-------------------------------------------------------

Running com.mycompany.alfresco.webscripts.HelloWorldWebScriptIT

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.53 sec - in com.mycompany.alfresco.webscripts.HelloWorldWebScriptIT

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]

[INFO] --- tomcat7-maven-plugin:2.2:shutdown (stop-tomcat) @ integration-test-runner ---

Jan 17, 2015 5:57:13 PM org.apache.coyote.AbstractProtocol pause

INFO: Pausing ProtocolHandler ["http-bio-8080"]

This ends the alfresco.war customization section.

Tracking down what plugin version that is used and what dependencies that are included

$ mvn clean install -Dsurefire.junit4.upgradecheck -Penable-it

$ mvn -Penable-it help:effective-pom > effectivepom.txt

Customizing the Share.war

This section goes through how to set up extension projects for the Share.war web application (i.e. the Alfresco Share web application that is the main user interface). We will look at how to set up a so called JAR extension, which will have one simple Aikau page that we will use to demonstrate the new functionality that is achieved with the Maven Tomcat plugin and the Spring Loaded library. We will see how we can change the page and widget and have those changes take immediate effect in a running Alfresco Share web application. All this new functionality will shorten the development cycle significantly.

Most of the work has actually already been done by setting up the tomcat-runner project, we just need to tell it to also run the share.war web application.

This section will also walk through how to do Unit Testing and Integration testing.

Create a Maven Project for an Alfresco Share JAR extension

We are going to start off by creating a new sub project that will hold the Share customizations for Component A. We are not going to use an AMP in this case either, just a simple JAR extension.

Call the project component-a-share-jar and create it under the alfresco-maven-sdk2-test parent directory with the following folder structure:

I have prepared the folder structure under the /resource directory to be able to contain Aikau pages, which are just defined as Web Scripts. These pages will use Aikau JavaScript widgets so I have created directories to hold widget stuff under /META-INF. I am not going to dig into and explain the Aikau framework in this article, will have to wait for next blog. I have also done a barebone POM for the project that you can see in the above picture to the right, it just brings in the parent POM. This POM is also part of the parent POM modules section.

Adding an Aikau Page to the Component A Share JAR extension

An Aikau page is just a Web Script that refers to one or more widgets. Let’s continue with the Hello World theme and create such as page. Start by creating the 3 files we need for the Web Script as follows:

The Aikau page Web Script implementation is really simple and the files that are part of it have the following content:

helloworld-page.get.desc.xml:

<webscript>

        <shortname>Hello World Aikau Page</shortname>

        <description>Hello World Aikau page definition</description>

        <family>Share</family>

        <url>/ecmstuff/helloworld-page</url>

</webscript>

helloworld-page.get.html.ftl:

<@processJsonModel group="share"/>

helloworld-page.get.js:

model.jsonModel = {

        widgets: [{

            id: "SET_PAGE_TITLE",

            name: "alfresco/header/SetTitle",

            config: {

                title: "This is a Hello World page!"

            }

        },

        {

            id: "MY_HORIZONTAL_WIDGET_LAYOUT",

            name: "alfresco/layout/HorizontalWidgets",

            config: {

                widgetWidth: 50,

                widgets: [

                    {

                        name: "alfresco/logo/Logo",

                        config: {

                            logoClasses: "alfresco-logo-only"

                        }

                    },

                    {

                      name: "mycompanyWidgets/HelloWorldWidget"

                    }

                ]

            }

        }]

};

Ok, so what we got here is a page that will have two widgets, one title widget and one layout widget. The layout widget also has two nested widgets, an Alfresco logo and the Hello World widget. The Hello World widget is new so we need to define it, create the following files under META-INF:

The Hello World widget implementation is really simple and the files that are part of it have the following content:

HelloWorldWidget.js:

define(["dojo/_base/declare",

            "dijit/_WidgetBase",

            "alfresco/core/Core",

            "dijit/_TemplatedMixin",

            "dojo/text!./HelloWorldWidget.html"

        ],

        function(declare, _Widget, Core, _Templated, template) {

            return declare([_Widget, Core, _Templated], {

                templateString: template,

                i18nRequirements: [ {i18nFile: "./HelloWorldWidget.properties"} ],

                cssRequirements: [{cssFile:"./HelloWorldWidget.css"}],

                buildRendering: function mycompany_widgets_HelloWorldWidget__buildRendering() {

                    this.greeting = this.message('helloworld.label');

                    this.inherited(arguments);

                }

            });

});

HelloWorldWidget.css:

.helloworld-widget {

        border: 1px #000000 solid;

        padding: 1em;

        width: 100px;

}

HelloWorldWidget.properties:

helloworld.label=Hello World!

HelloWorldWidget.html:

<div class="helloworld-widget">${greeting}</div>

The last thing we need to do is hook this JavaScript widget up with the Dojo JavaScript framework.

This is what the mycompany-widgets.xml file is for:

<extension>

        <modules>

            <module>

                <id>Hello World Aikau Widgets</id>

                <version>1.0</version>

                <auto-deploy>true</auto-deploy>

                <configurations>

                    <config evaluator="string-compare" condition="WebFramework" replace="false">

                        <web-framework>

                            <dojo-pages>

                                <packages>

                                    <package name="mycompanyWidgets" location="js/mycompany/aikau/widgets"/>

                                </packages>

                            </dojo-pages>

                        </web-framework>

                    </config>

                </configurations>

            </module>

        </modules>

</extension>

This is all there is to defining an Aikau page with widgets. Next step is to build it and include it in the customized share.war.

Make sure it builds and generates the JAR as follows:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn clean install

The extension JAR can now be found here:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-share-jar/target$ ls -l

total 20

...

-rw-rw-r-- 1 mbergljung mbergljung 6124 Jan 18 13:49 component-a-share-jar.jar

This JAR can now be dropped into an exploded Share WAR app.

Testing the Hello World Aikau Page from a Web Browser

The component A Share JAR is now built and contains the Hello World Page. To test it we can just drop the JAR file into the /alfresco/tomcat/webapps/share/WEB-INF/lib directory of our Alfresco 5.0 Community installation and restart Alfresco Tomcat.

Then use the http://localhost:8080/share/page/hdp/ws/ecmstuff/helloworld-page URL to try out the new page as follows:

We can see that it all works and our widgets are visible. Next step is to set up a project that can build the customized share.war.

Adding a Module to Build the Customized Alfresco Share WAR

This is something that we need to do before we can get the tomcat-runner project to also deploy and run the customized share.war. Tomcat will want the share.war file with the Component A Share JAR extension integrated. Start by adding a new subdirectory called share-war, with standard Maven directory structure and a POM, under the parent alfresco-maven-sdk2-test directory (you could copy the POM from the alfresco-war project to get a head start):

The POM is very similar to the one for the Alfresco WAR project and looks like this:

The POM sets the packaging to war as we want to create a customized share.war. Then we hook this project up to our parent POM, don’t forget to also include this project in the parent’s modules definition. After that follows a number of dependencies:

At this point we could build and produce a WAR that contains the component-a-share-jar extension. However, we want to prepare for any AMPs (Alfresco Module Packages) being added to the WAR in the future. And as Maven knows nothing about AMP files by default we need to include the maven-war-plugin in the build section so the project is prepared to include AMPs as well (we could also use the WAR plugin to specify JARs overlay order):

Note that JAR dependencies does not have to be specified as overlays, they are automatically overlayed. OK, so we now got a share.war with the Component A Share JAR extension but we still got no major improvement and have to stop Alfresco, replace the WAR, and then restart to see any updates we do to the Hello World Page. But we are prepared for running this WAR in an embedded Tomcat, which is next.

Running the Custom share.war with the Tomcat 7 Maven Plugin

The tomcat-runner project is prepared and ready so we just need to add another virtual webapp context for the share.war web application:

The share-webapp-context.xml file looks like this:

We are going to use the context to locate new Java classes and new JavaScript widgets. The Resources points to the component-a-share-jar resource location, which is the META-INF directory. The path is set to /res as all resources are loaded via a /share/res mapped Servlet. If we have any Alfresco Share modules that are implemented as AMPs, and contain static resources, then we would have to update the extra resource paths to look something like this:

<Resources className="org.apache.naming.resources.VirtualDirContext"

extraResourcePaths="/res=${project.parent.basedir}/component-a-share-jar/target/classes/META-INF;${project.parent.basedir}/component-b-share-amp/target/component-b-share-amp/web" />

The Loader configuration is setup to load any of the Component A Share JAR classes or test classes. Similar to the Resource configuration, if we have any Alfresco Share modules that are implemented as AMPs, and they got any classes, then we would have to update the virtual classpath to look something like this:

<Loader className="org.apache.catalina.loader.VirtualWebappLoader"

        searchVirtualFirst="true"

virtualClasspath="${project.parent.basedir}/component-a-share-jar/target/classes;${project.parent.basedir}/component-a-share-jar/target/test-classes;${project.parent.basedir}/component-b-share-amp/target/classes;${project.parent.basedir}/component-b-share-amp/target/test-classes;${project.parent.basedir}/component-b-share-amp/target/component-b-share-amp/config" />

The next thing we need to do is add the context file to the tomcat-runner plugin configuration:

This is it, we should now be able to start it up and access Alfresco Share (Note. Make sure the Alfresco 5.0 Community installation is not running):

mbergljung@brutor:~/src/alfresco-maven-sdk2-test$ mvn clean install -Prun

Wait for Tomcat to start:

INFO: WSSERVLET12: JAX-WS context listener initializing

Jan 20, 2015 2:58:24 PM com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>

INFO: WSSERVLET14: JAX-WS servlet initializing

Jan 20, 2015 2:58:25 PM org.apache.coyote.AbstractProtocol start

INFO: Starting ProtocolHandler ["http-bio-8080"]

We can now access Tomcat on http://localhost:8080, which will display our index.html page as follows:

Click on the Alfresco Share link (note. the Solr link does not yet work), you should see the following:

We will see some errors in some of the Dashlets that uses searches as Solr is not yet loaded. The log will also show messages such as:

Caused by: org.alfresco.repo.search.impl.lucene.LuceneQueryParserException: 00200007 Request failed 404 /solr/alfresco/alfresco?wt=json&fl=DBID%2Cscore&rows=50&df=TEXT&start=0&locale=en_GB&alternativeDic=DEFAULT_DICTIONARY&sort=%40cm%3Amodified+desc&fq=%7B%21afts%7DAUTHORITY_FILTER_FROM_JSON&fq=%7B%21afts%7DTENANT_FILTER_FROM_JSON

Indicating that Solr is not working, we will fix this later.

Finally, try the custom Hello World Page and make sure it works:

Update the Hello World Page and Watch Changes Take Effect Immediate

OK, we are finally at the point where we should be able to see the benefits from this setup. However, there is one little thing we need to add so we can get automatic refresh of Web Scripts after a compile and automatic clearing of cache dependencies. We need this as otherwise our changes will not be seen in the UI without a Tomcat restart. The page is based on a Web Script implementation so we need to refresh it if we change it, such as adding an Aikau Widget or changing the layout. If we make changes to Widgets, or add new once, they will not take effect unless we clear the dependency caches first.

We will fix this with a new profile in the parent project that we will call clear-caches-refresh-webscripts. This profile will use Groovy and the RESTClient to make some REST calls to the Share web application:

From now on keep this profile enabled in your IDE.

Let’s start making some changes to the page. First add the word Great to thee page title and then change the layout to Vertical in the Web Script’s controller:

model.jsonModel = {

        widgets: [{

            id: "SET_PAGE_TITLE",

            name: "alfresco/header/SetTitle",

            config: {

                title: "This is a Hello World page! Great!"

            }

        },

        {

            id: "MY_HORIZONTAL_WIDGET_LAYOUT",

            name: "alfresco/layout/VerticalWidgets",

            config: {

                widgetWidth: 50,

                widgets: [

                    {

                        name: "alfresco/logo/Logo",

                        config: {

                            logoClasses: "alfresco-logo-only"

                        }

                    },

                    {

                      name: "mycompanyWidgets/HelloWorldWidget"

                    }

                ]

            }

        }]

};

Now compile it so the file is moved to /target and so the Web Scripts are refreshed:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-repo-jar$ mvn compile

In the UI refresh the page:

Pretty cool! Now let’s make it more interesting and change the style and text of the custom Widget:

HelloWorldWidget.css:

.helloworld-widget {

        border: 1px #000000 solid;

        padding: 1em;

        width: 100px;

        color: red;

}

HelloWorldWidget.properties:

helloworld.label=Hello World! Easy!

HelloWorldWidget.html:

<div class="helloworld-widget">${greeting} Extra stuff!</div>

Now compile it so the files are moved to /target and so the dependency caches are cleared:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-repo-jar$ mvn compile

In the UI refresh the page:

Not bad without any restarts. Now let’s add a completely new Widget as follows:

The implementation is as follows:

HelloWorldWidget2.js:

define(["dojo/_base/declare",

            "dijit/_WidgetBase",

            "alfresco/core/Core",

            "dijit/_TemplatedMixin",

            "dojo/text!./HelloWorldWidget2.html"

        ],

        function(declare, _Widget, Core, _Templated, template) {

            return declare([_Widget, Core, _Templated], {

                templateString: template,

                i18nRequirements: [ {i18nFile: "./HelloWorldWidget2.properties"} ],

                cssRequirements: [{cssFile:"./HelloWorldWidget2.css"}],

                buildRendering: function mycompany_widgets_HelloWorldWidget2__buildRendering() {

                    this.greeting2 = this.message('helloworld.label2');

                    this.inherited(arguments);

                }

            });

});

HelloWorldWidget2.css:

.helloworld-widget2 {

        border: 5px #000000 solid;

        padding: 1em;

        width: 50px;

        color: blue;

}

HelloWorldWidget2.properties:

helloworld.label2=Hello World Again!

HelloWorldWidget2.html:

<div class="helloworld-widget2">${greeting2}</div>

And let’s add this new widget to the page:

model.jsonModel = {

        widgets: [{

            id: "SET_PAGE_TITLE",

            name: "alfresco/header/SetTitle",

            config: {

                title: "This is a Hello World page! Great!"

            }

        },

        {

            id: "MY_HORIZONTAL_WIDGET_LAYOUT",

            name: "alfresco/layout/VerticalWidgets",

            config: {

                widgetWidth: 50,

                widgets: [

                    {

                        name: "alfresco/logo/Logo",

                        config: {

                            logoClasses: "alfresco-logo-only"

                        }

                    },

                    {

                        name: "mycompanyWidgets/HelloWorldWidget"

                    },

                    {

                        name: "mycompanyWidgets/HelloWorldWidget2"

                    }

                ]

            }

        }]

};

Now compile it so the files are moved to /target and so the dependency caches are cleared and web scripts are refreshed:

mbergljung@brutor:~/src/alfresco-maven-sdk2-test/component-a-repo-jar$ mvn compile

In the UI refresh the page:

Awesome, can you see how easy it is going to be to build Aikau pages with this setup!

OK, what about Dashlets? Easy, just add the Web Script implementation for it and compile and refresh UI. I tested with the following Alfresco example Dashlet:

helloworld-dashlet.get.desc.xml:

<webscript>

        <shortname>Hello World Aikau Dashlet</shortname>

        <description>Hello World Aikau Dashlet definition</description>

        <family>dashlet</family>

        <url>/ecmstuff/helloworld-dashlet</url>

</webscript>

helloworld-dashlet.get.html.ftl:

<@markup id="css" >

        <#-- CSS Dependencies -->

</@>

<@markup id="js">

        <#-- JavaScript Dependencies -->

</@>

<@markup id="widgets">

        <@createWidgets group="dashlets"/>

</@>

<@markup id="post">

</@>

<@markup id="html">

        <@uniqueIdDiv>

            <div class="dashlet">

                <div class="title">

                    ${msg("header.label")}

                </div>

                <div class="body dashlet-padding">

                   Hello world!

                </div>

            </div>

        </@>

</@>

helloworld-dashlet.get.js:

function widgets()

{

        var dashletTitleBarActions = {

            id : "DashletTitleBarActions",

            name : "Alfresco.widget.DashletTitleBarActions",

            useMessages : false,

            options : {

                actions: [

                {

                    cssClass: "help",

                    bubbleOnClick:

                    {

                        message: msg.get("dashlet.help")

                    },

                    tooltip: msg.get("dashlet.help.tooltip")

                }

                ]

            }

        };

        model.widgets = [dashletTitleBarActions];

}

widgets();

helloworld-dashlet.get.properties, helloworld-dashlet.get_en.properties:

header.label=Custom Dashlet Example

dashlet.help=This is an example dashlet

Adding Search

This section adds the search functionality to complete the build project.

Adding Solr Search to the Embedded Tomcat

The last thing we need to set up to complete the Alfresco build project is to add the Apache Solr web application so we can search for content from the Alfresco Share user interface. This is a customized Solr application so we will setup a dependency for it so the WAR gets downloaded from the Alfresco Nexus artifactory. We also want to use plain HTTP connections between Solr.war and Alfresco.war so we don’t have to bother with setting up keystores, users, and connection config in Tomcat. It is also faster with HTTP instead of HTTPS when we test stuff.

All this means that the solrconfig.xml and solrcore.properties files for each core will have to have some properties dynamically set so Solr can run locally when embedded in Tomcat. The property values will be either set in our parent project or picked up from default settings in the Alfresco SDK parent.

Similar to the other WAR projects, we start off by creating a new sub-project called solr-war that has the files mentioned above plus the usual POM file:

I have also added another file under the assembly directory called solr-config-assembly.xml. it will contain instructions on what to include when packaging/assembling a customized solr-config.zip, such as excluding the index files themselves.

Let’s look at an overview of what we have to do to put together all the pieces to get Solr to run locally, here is a table of Maven phases, in the order they are executed, and the plugins that are used in each phase (the plugins are executed in the order they are defined):

Phase

Plugin

Operation  Performed

generate-resources

maven-dependency-plugin

Will run goal unpack and unzip the content of the downloaded default solr-config.zip file into the alfresco-maven-sdk2-test/solr-war/solr_home directory (The solr-config.zip is brought in via the dependency section)

prepare-package

maven-resources-plugin

maven-war-plugin

replacer

Will run goal copy-resources and take the config files we got under alfresco-maven-sdk2-test/solr-war/src/resources/solr-properties and filter them to substitute properties and then copy the files over to the alfresco-maven-sdk2-test/solr-war/solr_home directory, overwriting the default config files.

Will run the goal exploded and produce an exploded Solr webapp in the alfresco-maven-sdk2-test/solr-war/target/solr directory, ready for us to update the web.xml file (The solr.war that is exploded is brought in via the dependency section)

Will run the goal replace and update the web.xml where applicable, commenting out the security-constraints so we can run with HTTP

package

maven-assembly-plugin

Will run goal single and package/assemble the customized solr-config.zip file based on the content in the alfresco-maven-sdk2-test/solr-war/solr_home directory, excluding the index files.

 

So what we are doing is basically this, we start by unpacking the default solr configuration ZIP, brought in via dependency definition, into the solr-war/solr_home directory. Then we override the configuration properties files so we can communicate locally over HTTP. We also unpack the default solr web application WAR, brought in via dependency definition, into the /target/solr directory so we can update the web.xml file and communicate over HTTP.

The POM begins like this:

See source code for the rest...