Anatomy of an ACS 6 AMP project

cancel
Showing results for 
Search instead for 
Did you mean: 

Anatomy of an ACS 6 AMP project

stefankopf
Alfresco Employee
8 3 4,196

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.

3 Comments
binduwavell
Partner

Awesome write up Stefan, thank you!

In the past when we relied on SpringJUnit4ClassRunner to spin up an application context, we would not get a fully running repo since there was no application server, we were not able to for example fully exercise webscripts in our integration tests. 

I have heard rumors that ACS 6 may use SpringBoot with the embedded runner stuff, so this may no longer be an issue. Thoughts?

Thanks again!

— Bindu

stefankopf
Alfresco Employee

Hi Bindu,

Nice to see that this blog post was helpful.

You are right: The tests described here just start a spring context, but not a full repository. They are essentially unit tests, and not real integration tests.

But we need to run them in the integration-test phase in maven so that we can start and stop the database container in the pre- and post- phases. (There are no pre-test or post-test phases in maven).

When we do integration testing, then we add another project to a multi-module maven build just for integration testing. This project is set up as a war project and adds the amp we build on top of a plain vanilla alfresco.war.

In the pre-integration-test phase we then start the docker container with the DB as well as a tomcat with the combined war. In the integration-test phase, we then run either REST calls against the repo or solium tests against the UI (e.g. admin console).

I might write another blog post about these integration tests in the future.

In ACS6 we still use Tomcat and not Spring Boot. What's new is that we pack the components in docker containers and orchestrate everything with k8s.

You can already see this in the last two community releases. If you are interested in the details, you can take a look at the Dockerfile that pulls everything together.

Stefan

binduwavell
Partner

Thanks very much for this detailed reply Stefan!