in Alfresco

Alfresco 5 and Spring tutorial

Introduction

For a long time I am using alfresco sdk and it is just awesome, it is really easy to create new project, to add unit tests and to run integration test #loveit.

As you probably know alfresco sdk tutorials are always using basic spring xml configuration to add webscripts , beans, services and so on BUT not any more in this tutorial you are going to see how to use spring annotations :

  • @Service
  • @Transactional
  • @Value

You are going to see how to set method security.  Additionally you are going to see how to add javascript service.

Creating project

Before we can start adding beans we need to create a project, using super easy command and we can start maven wizard

mvn archetype:generate -Dfilter=org.alfresco:

After interactive mode is started you are able to select one of several archetypes

  • alfresco-allinone-archetype

  • alfresco-amp-archetype

  • share-amp-archetype

for this tutorial we are going to select the second archetype alfresco-amp-archetype. Select option by entering 2 . Enter basic properties of the project and you are ready to go!

Basic commands

After you import the project in eclipse ( or what ever you are into) you can see that this project has more then few things inside. It has two beans Demo, DemoComponent and one webscript named HelloWorldWebScript defined in  service-context.xml. If you take a look at the test part of the project you will notice one Test class DemoComponentTest.

Let go through some basic commands that you can run now

  • Check if tests are OK
    mvn clean test
  • Package your module in amp file
    mvn clean package
  • Start integration test
    mvn clean integration-test -Pamp-to-war
  • use run.sh to run integration test with spring reload
    ./run.sh

Power of spring

Beans

Lets see how to add new spring bean using annotations. We are going to create interface Knight and one class that implements it called MyKnight.

public interface Knight {
 String getName();
}

@Service(value="MyKnight")
public class MyKnight implements Knight {

 private String name = "Ares the Great";

 @Override
 public String getName() {
 return name;
 }

}

Ok so we have some @Service annotation here , lets see what happens when we add wire test in the DemoComponentTest and run the test.

@Autowired
@Qualifier(value = "myKnight")
private Knight knight;
 @Test
 public void knightTest() {
 assertEquals("Ares the Great", knight.getName());
 }

Oh test did not pass :(

 DemoComponentTest.knightTest » BeanCreation Error creating bean with name 'com...

Reason for this is that we did not add component-scan in the service-context.xml file.

 <context:component-scan base-package="com.alfrescoblog"></context:component-scan>

Lets not forget to add some xmlns in beans tag…

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">

This is how you can add any bean you like using annotations. Wiring the bean is super easy and you can use

@Resource 
private Knight knight;

Webscripts

In the world of REST it is a common case that we just want to have new REST that can return json. You are going to learn how to do this now.

Checklist

  1. Create a class GetKnight that extends AbstractWebScript and override the execute method
  2. Add @Service annotation
    @Service(value = "webscript.com.alfrescoblog.rest.knight.get")
  3. Add desc.xml file on this location
     amp/config/alfresco/extension/templates/webscripts/com/alfrescoblog/rest/knight.get.desc.xml
  4. Verify that service value starts with webscript, ends with get, put, delete, post.
  5. Verify that location of desc.xml file is matching the service value.

Let see all of the code

GetKnight.java

import com.google.gson.Gson;//yes I have added gson dependency in pom.xml

@Service(value = "webscript.com.alfrescoblog.rest.knight.get")
public class GetKnight extends AbstractWebScript {

 @Resource
 Knight knight;

 @Override
 public void execute(WebScriptRequest req, WebScriptResponse res) {
   Gson gson = new Gson();
   String json = gson.toJson(knight);
   try {
     res.getWriter().write(json);
   } catch (IOException e) {
    e.printStackTrace();
   }
 }
}

If you take a look you will see that we are converting the knight instance to json.

knight.get.desc.xml

<webscript>
 <shortname>Get The Knight</shortname>
 <description>Get The Knight</description>
 <url>/knight</url>
 <authentication>user</authentication>
 <format default="json"></format>
</webscript>

Lets test if this works, start ./run.sh and go to next url

http://localhost:8080/alfresco/service/knight

Check if result matches

{"name":"Ares the Great"}

#awesome!

Transactional

As we know transactions are important, they are important when we want to make a number of modifications and would like automagical rollback when error occurs. To enable ussage of @Transactional annotation we need to add

 <tx:annotation-driven />

to service-context.xml. Also we need to add some namespaces.

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:util="http://www.springframework.org/schema/util"
 xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
">

Class MyKnight and @Transactional example.

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service(value="myKnight")
public class MyKnight implements Knight {

 private String name = "Ares the Great";

 @Override
 @Transactional
 public String getName() {
    return name;
 }
}

Using @Value annotation

This module may be simple but usually modules have custom properties that configure it. In the configuration we can have custom urls, and other settings that are specific to your module. Find alfresco-global.properties and the property

knight.weapon=Sword

To wire value “Sword” as a attribute of your class use @Value annotation. Here is the code that demonstrates this.

@Service(value = "myKnight")
public class MyKnight implements Knight {

 private String name = "Ares the Great";

 @Value(value = "${knight.weapon}")
 private String weapon;

 @Override
 public String getWeapon() {
 return weapon;
 }
...

Now our knight will have a weapon in his hand :) and a fighting chance.

JavaScript Service

Every now and then I need to add a service and to be able to call it from my javascript code. Here is the example of how to do that. First we are going to create one abstract javascript service.

@Service(value = "AbstractScriptService")
public abstract class AbstractScriptServiceImpl extends BaseScopableProcessorExtension {

 @Resource(name = "ServiceRegistry")
 private ServiceRegistry serviceRegistry;

 public AbstractScriptServiceImpl() {
 }

 @Override
 @PostConstruct
 public void register() {
  super.register();
 }

 @Override
 @Value(value = "#{javaScriptProcessor}")
 public void setProcessor(Processor processor) {
  super.setProcessor(processor);
 }

 public ServiceRegistry getServiceRegistry() {
  return serviceRegistry;
 }

 public ActivitiNodeConverter getNodeConverter() {
  return new ActivitiNodeConverter(this.serviceRegistry);
 }
}

Now lets see how to create a real script service called ScriptKnightService.

@Service(value = "ScriptKnightService")
public class ScriptKnightService extends AbstractScriptServiceImpl {
 @Resource(name="myKnight)
 private Knight myKnight;
 
 @Override
 @Value(value = "knightService")
 public void setExtensionName(String extension) {
   super.setExtensionName(extension);
 }
 public String getKnightWeapon() throws Exception {
  return myKnight.getWeapon();
 }
}

In our javascript file we are going to use ScriptKnightService like this:

var weapon = knightService.getKnightWeapon();

Unit tests

We already have alfresco test when we have created our project, but this test has a problem. Lets observe the code and see before method that prepares the data for other tests.

 @org.junit.Before
 public void before() {
   AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER_NAME);
   AuthorityService authorityService = serviceRegistry.getAuthorityService();
 
   String knightGroup = authorityService
     .createAuthority(AuthorityType.GROUP, "KNIGHTS");
   serviceRegistry.getAuthorityService().addAuthority(knightGroup, ADMIN_USER_NAME);
  }

As you can see nothing is awesome here, we are creating a Knights group and user admin is set as a member  because  he is an awesome knight. If you run this test all will go smooth but if you repeat this you will see an exception :( :

org.alfresco.service.cmr.repository.DuplicateChildNodeNameException:

Why? Reason for this is that transaction is not rolled back after test finishes. To fix this we need to improve our test a bit by adding two annotations like so

@RunWith(RemoteTestRunner.class)
@Remote(runnerClass = SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:alfresco/application-context.xml")
@TransactionConfiguration
   (transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class DemoComponentTest{
...
}

now we can create new nodes and run tests again and again.

Method security

Previously we have created new group called KNIGHT where only holy knights will be in, if we recall bean myKnight we will see that knight has a method named getWeapon(). Only holy knight should be able to execute this method as we would not like anyone else who is not noble to do this, right? Another method is getName() this method we will allow only authenticated users to run. Ok so we are going to set this up:

  • getWeapon(), only Holy knights
  • getName(), only authenticated users

To achieve this we need some spring magic :

  1. We need one ProxyFactoryBean that targets bean myKnight using Knight interfaces and adds interceptor MyKnight_security
  2. We need to define bean MyKnight_security and specify security for each method
<bean id="MyKnight" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces">
    <list>
     <value>com.alfrescoblog.demoamp.Knight</value>
    </list>
  </property>
  <property name="target">
    <ref bean="myKnight" />
  </property>
  <property name="interceptorNames">
    <list>
     <idref local="MyKnight_security" />
    </list>
  </property>
 </bean>

<bean id="MyKnight_security"
 class="org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor">

 <property name="authenticationManager">
   <ref bean="authenticationManager" />
 </property>
 <property name="accessDecisionManager">
   <ref bean="accessDecisionManager" />
 </property>
 <property name="afterInvocationManager">
   <ref bean="afterInvocationManager" />
 </property>
 <property name="objectDefinitionSource">
   <value>
     com.alfrescoblog.demoamp.Knight.getWeapon=ACL_METHOD.GROUP_KNIGHTS
     com.alfrescoblog.demoamp.Knight.getName=ACL_METHOD.ROLE_AUTHENTICATED
   </value>
   </property>
</bean>

What is important to remember here is that you must wire the proxy bean MyKnight. This is achieved using

@Resource(name="MyKnight") 
Knight knight;

Alfresco comes packed with method permissions settings, let list few of the options that you will probably use.

  • ACL_METHOD.GROUP_KNIGHTS
  • ACL_METHOD.ROLE_AUTHENTICATED
  • ROLE_ADMINISTRATOR
  • ACL_DENY
  • ACL_ALLOW

Lets see what we learned here

In this tutorial you have learned how to

  • create alfresco project
  • use basic commands
  • add new service using annotations
  • add new webscript using annotations
  • return json object in the webscript
  • use @Value annotation
  • use @Transactional annotation
  • setup method security on your service

I hope that this tutorial will help you to solve your problems, if it does please do not forget to share to your friends.

Don't be shellfish...Tweet about this on TwitterShare on LinkedInShare on Google+Share on RedditShare on Facebook

Was this helpful ?