stefankopf

Anatomy of an ACS 6 AMP project

Blog Post created by stefankopf Employee on Apr 24, 2018

At first, I want to highlight that an AMP module is not the recommended way to extend or develop against ACS 6. Instead, you should build a (micro-)service that sits next to the repository and use the v1 REST API to talk to the repository.

However, there are certain requirements that cannot yet be met by an extension outside the ACS repository. And, of course, you simply might want to port existing extension projects to ACS 6.

There is also the official "Alfresco SDK" that can help you to jump start extension projects. But this SDK is not updated until after the release of a new ACS version. So we in engineering need to build AMPs independent of the SDK. In this post, I want to share our experiences in the hope that it contains some useful background information.


Basic project structure

We provide plugins for maven to build AMP files. The smallest possible AMP project has a pom.xml file that looks like this:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example.acs-module</groupId>
    <artifactId>my-awesome-module</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>amp</packaging>
    <properties>
        <app.amp.output.folder>${project.build.directory}/amp</app.amp.output.folder>
    </properties>
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/amp</directory>
                <targetPath>${app.amp.output.folder}</targetPath>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.alfresco.maven.plugin</groupId>
                <artifactId>alfresco-maven-plugin</artifactId>
                <version>2.2.0</version>
                <extensions>true</extensions>
            </plugin>
        </plugins>
    <build>
</project>

This defines amp as the packaging type of your project and brings in our alfresco-maven-plugin which provides this packaging. Our plugin is available through maven central so that you do not need to define an additional plugin repository.


Required files

Each AMP project should consist at least of these files:

/my-awesome-module
  |
  +-- pom.xml
  |
  +-- src
       |
       +-- main
            |
            +-- amp
            |    |
            |    +-- module.properties
            |
            +-- resources
                 |
                 +-- alfresco
                      |
                      +-- module
                           |
                           +-- my-awesome-module
                                |
                                +-- module-context.xml

The module.properties file defines this module and helps our module manager to do its job. It basically looks like this:

module.id=my-awesome-module
module.title=My awesome module
module.description=This is an awesome module
module.version=1.0
module.repo.version.min=6.0

The second file is the entry point to the Spring context that makes up the ACS server. You can define new beans in there that will be instantiated during startup and that can be wired up with other components of ACS.


Dependencies

If your project contains Java code, as almost all AMP projects do, then you need to define certain artifacts from the ACS 6 repository as dependencies:

<project>
    ...
    <packaging>amp</packaging>
    <dependencies>
        <dependency>
            <groupId>org.alfresco</groupId>
            <artifactId>alfresco-repository</artifactId>
            <version>6.37</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.alfresco</groupId>
            <artifactId>alfresco-remote-api</artifactId>
            <version>6.23</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>alfresco-public</id>
            <url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
        </repository>
    </repositories>
    <build>
        ...
    <build>
</project>

From ACS 6 onwards, each artifact is on its own lifecycle. You can look up the version numbers of each artifact either in the release notes or simply take a look in your ACS 6 deployment.
It is important to define each artifact as provided so that it is only used during the compilation of your Java classes, but it is not packaged into the AMP file.
If your customization depends on enterprise specific bits, then you need to include the artifacts alfresco-enterprise-repository and alfresco-enterprise-remote-api as well.

Since our artifacts are not always available through maven central, you should add our artifact repository to your build as shown above.


Testing

During the test phase in maven, you should only run isolated jUnit tests that do not require a full ACS repository.
Tests that require a repository, e.g. to create or modify nodes, should be run as integration tests.
The default configuration of the "surefire" (test) and "failsafe" (integration-test) plugins define wildcards for test cases. All classes in src/main/test with a name like *Test are run during the test phase and all classes with a name like *IT are run during the integration-test phase.
Your integration tests then might look like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( { "classpath:alfresco/application-context.xml" } )
@TestExecutionListeners( listeners = { DependencyInjectionTestExecutionListener.class,
                                       MyAwesomeModuleIT.class} )
public class MyAwesomeModuleIT extends AbstractTestExecutionListener
{

    @Autowired
    @Qualifier("NodeService")
    protected NodeService nodeService;

    @Autowired
    @Qualifier("FileFolderService")
    protected FileFolderService fileFolderService;

    @Before
    public void initializeTest()
    {
    }

    @After
    public void cleanupTest()
    {
    }

    @Test
    public void testAwesomeFeature() throws Exception
    {
    }

}

This definition of a jUnit test executes the tests in a Spring environment.The @ContextConfiguration of the spring jUnit runner then loads the main ACS context file which starts up the entire ACS 6 repository.

These jUnit tests require additional dependencies in your pom.xml file:

<project>
    ...
    <dependencies>
        ...
        <!-- test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.4.1212</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.0.6.RELEASE</version>
            <type>jar</type>
            <scope>test</scope>
        </dependency>
    </dependencies>
    ...
</project>

And we need to configure the maven-failsafe-plugin plugin to set the required ACS configuration properties as system properties:

<project>
    ...
    <build>
        <plugins>
            ...
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.17</version>
                <configuration>
                    <systemPropertyVariables>
                        <db.name>acs-test</db.name>
                        <db.driver>org.postgresql.Driver</db.driver>
                        <db.url>jdbc:postgresql://localhost:${database.port}/acs-test</db.url>
                        <dir.root>${project.build.directory}/alf-data-test</dir.root>
                    </systemPropertyVariables>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    <build>
</project>

The configuration above uses a PostgreSQL database on localhost for testing. We suggest to use docker to provide this temporary database instance during the integration tests:

<project>
    ...
    <build>
        <plugins>
            ...
            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>0.25.0</version>
                <configuration>
                    <images>
                        <image>
                            <alias>test-database</alias>
                            <name>postgres:9.4.12</name>
                            <run>
                                <ports>
                                    <port>database.port:5432</port>
                                </ports>
                                <env>
                                    <POSTGRES_PASSWORD>alfresco</POSTGRES_PASSWORD>
                                    <POSTGRES_USER>alfresco</POSTGRES_USER>
                                    <POSTGRES_DB>acs-test</POSTGRES_DB>
                                </env>
                                <cmd>
                                    <shell>-c max_connections=300</shell>
                                </cmd>
                                <wait>
                                    <log>database system is ready to accept connections</log>
                                    <time>20000</time>
                                </wait>
                            </run>
                        </image>
                    </images>
                </configuration>
                <executions>
                    <execution>
                        <id>start</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    <build>
</project>

This pulls the image postgres in version 9.4.12 from DockerHub and starts a new, clean container from this image. After running the integration tests, it stops and removes this image again.
This requires that you have Docker installed and set up properly on your machine.

Outcomes