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
- Create a class GetKnight that extends AbstractWebScript and override the execute method
- Add @Service annotation
@Service(value = "webscript.com.alfrescoblog.rest.knight.get")
- Add desc.xml file on this location
amp/config/alfresco/extension/templates/webscripts/com/alfrescoblog/rest/knight.get.desc.xml
- Verify that service value starts with webscript, ends with get, put, delete, post.
- 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 :
- We need one ProxyFactoryBean that targets bean myKnight using Knight interfaces and adds interceptor MyKnight_security
- 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.
Was this helpful ?