Obsolete Pages{{Obsolete}}
The official documentation is at: http://docs.alfresco.com
During the development of the Mediaclient demo application I figured out that Alfresco's Web Service API for Java could need a tutorial. So this tutorial aims to extend the following existing documentation:
About the Mediaclient demo application: The demo application is JBoss Seam based and was developed by using the 'Ingres Development Stack for JBoss'. The purpose of the application is to show how to develop a JBoss Seam based application which uses Alfresco as the file store. A second tutorial with further details will follow soon. The application itself can be used to manage MP3 files by storing them inside Alfresco.
About the Ingres Development Stack for JBoss: It contains the following components:
Further information can be found at http://info.ingres.com/g/?TATV5CKFX4 or http://esd.ingres.com/product/Enterpr.../JBoss.
About Ingres: Ingres is the vendor of the Open Source database system named 'Ingres Database'. Further information can be found at http://www.ingres.com or http://community.ingres.com . An article how to install Ingres with Alfresco can be found at http://wiki.alfresco.com/wiki/Installing_Alfresco_Labs_3b_and_Ingr....
A Java package named 'community.ingres.mediaclient.alfresco' was created. This package contains the following base classes:
The child classes of Client are:
So I will show in this tutorial how to use Alfresco's Web Service API for Java to perform uploads to, updates on and downloads from your Alfresco server.
There is only one example child of the class Aspect which is named:
We will talk about aspects later in this tutorial.
Additionally the following file is part of the package:
This file contains the connection information to your Alfresco server. In my case it contains the following:
server.host=localhost
server.port=8180
server.user=admin
server.password=admin
Let's begin to look at the implementation of the several clients!
The Client class contains all the stuff which is used by all the other clients.
The Client class has a global constant named STORE which is shared over all other clients. STORE defines Alfresco's default location to store data:
protected final Store STORE = new Store(Constants.WORKSPACE_STORE, 'SpacesStore');
It also has the following global variables:
// Connection properties
protected String server_host = '';
protected String server_port = '';
protected String server_username = '';
protected String server_password = '';
The connection properties we want to use are stored inside the file 'alfresco.properties'. So the following method can be used to bind the connection properties to our Client:
protected void setSessionProperties()
{
Properties prop = new Properties();
try
{
prop.load(getClass().getResourceAsStream('alfresco.properties'));
}
catch(Exception e)
{
System.err.println('Can not load alfresco.properties');
e.printStackTrace();
}
server_host = prop.getProperty('server.host');
server_port = prop.getProperty('server.port');
server_username = prop.getProperty('server.user');
server_password = prop.getProperty('server.password');
}
Before we can communicate with the Alfresco server we have to open a session with it. The following code can be used to open and close a session:
protected void startSession()
{
try
{
setSessionProperties();
System.out.println('Connecting to: ' + 'http://'+server_host+':'+server_port+'/alfresco');
WebServiceFactory.setEndpointAddress('http://'+server_host+':'+server_port+'/alfresco/api');
AuthenticationUtils.startSession(server_username, server_password);
}
catch(Exception e)
{
System.err.println('Can not initiate session with Alfresco server.');
e.printStackTrace();
}
}
protected void endSession()
{
System.out.println('Closing connection.');
AuthenticationUtils.endSession();
}
We will use the 'ContentService' and the 'RepositoryService' to communicate with our Alfresco server. Here you can see how to get these services:
protected RepositoryServiceSoapBindingStub getRepositoryService()
{
return WebServiceFactory.getRepositoryService();
}
protected ContentServiceSoapBindingStub getContentService()
{
return WebServiceFactory.getContentService();
}
The method 'ReferenceToParent(Reference)' transforms the given node reference to a parent reference. The method 'normilizeNodeName(String)' is used to validate the paths we will use as part of references.
protected ParentReference ReferenceToParent(Reference spaceref)
{
ParentReference parent = new ParentReference();
parent.setStore(STORE);
parent.setPath(spaceref.getPath());
parent.setUuid(spaceref.getUuid());
parent.setAssociationType(Constants.ASSOC_CONTAINS);
return parent;
}
//Blanks are allowed in space names but not in paths. Because the path should depend on the name we need a version of the name which contains no blanks
protected String normilizeNodeName(String name)
{
return name.replace(' ','_');
}
We will create all our documents in folders under 'Company Home'. To reference the 'Company Home' space the following code could be used:
protected ParentReference getCompanyHome()
{
ParentReference companyHomeParent = new ParentReference(STORE, null, '/app:company_home', Constants.ASSOC_CONTAINS, null);
return companyHomeParent;
}
As you can see we are working with 'References', which means references to nodes. A node could be a space or a document. A parent reference is a special kind of reference and indicates that it contains children. The path of the Company Home space is '/app:company_home'.
The purpose of this client (surprise 😉 ) is to upload documents to Alfresco. This chapter covers how to create spaces and documents.
To create a space under 'Company Home' the following code can be used:
protected Reference createSpace(String spacename) throws Exception
{
Reference space = null;
// Create the space if it is not already existent
try {
//Therefore a reference to the maybe not existent space is required
System.out.println('Entering space ' + spacename);
space = new Reference(STORE, null, getCompanyHome().getPath() + '/cm:' + normilizeNodeName(spacename));
getRepositoryService().get(new Predicate(new Reference[]{space}, STORE, null));
}
catch (Exception e1)
{
System.out.println('The space named ' + spacename + ' does not exist. Creating it.');
ParentReference companyHome = getCompanyHome();
// Set Company Home as the parent space
companyHome.setChildName(Constants.createQNameString(Constants.NAMESPACE_CONTENT_MODEL, normilizeNodeName(spacename)));
//Set the space's property name
NamedValue[] properties = new NamedValue[]{Utils.createNamedValue(Constants.PROP_NAME,spacename)};
// Create the space using CML (Content Manipulation Language)
CMLCreate create = new CMLCreate('1', companyHome, null, null, null, Constants.TYPE_FOLDER, properties);
CML cml = new CML();
cml.setCreate(new CMLCreate[]{create});
//Execute the CML create statement
try {
getRepositoryService().update(cml);
} catch (Exception e2) {
System.err.println('Can not create the space.');
throw e2;
}
}
return space;
}
In summary the following steps are performed:
The way is nearly the same as in the previous section, but additionally we pass a reference to the parent space.
protected Reference createSpace(Reference parentref, String spacename) throws Exception
{
Reference space = null;
ParentReference parent = ReferenceToParent(parentref);
try {
System.out.println('Entering space ' + spacename);
space = new Reference(STORE, null, parent.getPath() + '/cm:' + normilizeNodeName(spacename));
WebServiceFactory.getRepositoryService().get(new Predicate(new Reference[]{space}, STORE, null));
}
catch (Exception e1)
{
System.out.println('The space named ' + spacename + ' does not exist. Creating it.');
parent.setChildName(Constants.createQNameString(Constants.NAMESPACE_CONTENT_MODEL, normilizeNodeName(spacename)));
//Set the space's property name
NamedValue[] properties = new NamedValue[]{Utils.createNamedValue(Constants.PROP_NAME, spacename)};
// Create the space using CML (Content Manipulation Language)
CMLCreate create = new CMLCreate('1', parent, null, null, null, Constants.TYPE_FOLDER, properties);
CML cml = new CML();
cml.setCreate(new CMLCreate[]{create});
//Execute the CML create statement
try {
getRepositoryService().update(cml);
} catch (Exception e2) {
System.err.println('Can not create the space.');
throw e2;
}
}
return space;
}
So we have methods to create a space inside the root space and in another space. The following example shows how these methods could be used:
startSession();
//To create the space 'Comany Home/Mediaclient/artist_name/album_name/title_name'
Reference r = createSpace(createSpace(createSpace('Mediaclient'), artist_name), album_name),title_name);
endSession();
A document is always a child of a space.
protected Reference createDocument(Reference parentref, String docname, byte[] content) throws Exception
{
Reference document = null;
ParentReference parent = ReferenceToParent(parentref);
parent.setChildName(Constants.createQNameString(Constants.NAMESPACE_CONTENT_MODEL, normilizeNodeName(docname)));
NamedValue[] properties = new NamedValue[]{Utils.createNamedValue(Constants.PROP_NAME, docname)};
CMLCreate create = new CMLCreate('1', parent, null, null, null, Constants.TYPE_CONTENT, properties);
CML cml = new CML();
cml.setCreate(new CMLCreate[]{create});
//Execute the CML create statement
UpdateResult[] results = null;
try {
System.out.println('Creating the document ' + docname);
results = getRepositoryService().update(cml);
document = results[0].getDestination();
}
catch (Exception e)
{
System.err.println('Can not create the document.');
throw e;
}
//Set the content
ContentFormat format = new ContentFormat(Constants.MIMETYPE_TEXT_PLAIN, 'UTF-8');
try {
System.out.println('Setting the content of the document');
getContentService().write(document, Constants.PROP_CONTENT, content, format);
} catch (Exception e2) {
System.err.println('Can not set the content of the document.');
throw e2;
}
return document;
}
In addition to the steps those were already explained for the 'createSpace' method, the content of the document must be set. Be aware of that the method above does create a text document without a special aspect. The UploadClient does also contain another method with the following signature:
It allows to specify the document's mime_type and aspect. I will explain it later in the chapter 'User defined Aspects'.
The following example shows how you can use the createDocument method to upload a file to a specific space. If any of the spaces is not existent it will be created.
public String uploadFile(String name, byte[] data) throws Exception
{
startSession();
Reference r = createDocument(createSpace(createSpace('Mediaclient'),'Uploads'),name,data);
endSession();
return r.getPath();
}
This client enables us to delete, move or rename nodes.
protected void deleteSpace(Reference space) throws Exception
{
CMLDelete delete = new CMLDelete(new Predicate(new Reference[]{space},null,null));
CML cml = new CML();
cml.setDelete(new CMLDelete[]{delete});
//Execute the CMLDelete statement
try {
System.out.println('Deleting the space ' + space.getPath());
WebServiceFactory.getRepositoryService().update(cml);
} catch (Exception e2) {
System.err.println('Can not delete the space.');
throw e2;
}
}
A document can be deleted the same way as a space.
The renameSpace method needs to know where a space is located. Therefore the following method is used:
protected Reference getParent(Reference space) throws Exception
{
QueryResult result = getRepositoryService().queryParents(space);
ResultSet resultset = result.getResultSet();
String first_parent_id = resultset.getRows(0).getNode().getId();
System.out.println('The queried parent id is: ' + first_parent_id);
Reference parent = new Reference(STORE, first_parent_id, null);
return parent;
}
In summary the RepositoryService's method 'queryParents(Reference)' is used to get the parent of a space.
protected void moveSpace(Reference space, Reference dest, String newname) throws Exception
{
ParentReference parentDest = ReferenceToParent(dest);
parentDest.setChildName(Constants.createQNameString(Constants.NAMESPACE_CONTENT_MODEL, normilizeNodeName(newname)));
CMLMove move = new CMLMove();
move.setTo(parentDest);
move.setWhere(new Predicate(new Reference[]{space},STORE,null));
NamedValue[] properties = new NamedValue[]{Utils.createNamedValue(Constants.PROP_NAME,newname)};
CMLUpdate update = new CMLUpdate();
update.setProperty(properties);
update.setWhere(new Predicate(new Reference[]{space},STORE,null));
CML cml = new CML();
cml.setMove(new CMLMove[]{move});
cml.setUpdate(new CMLUpdate[]{update});
//Execute the CML move and Update statement
try {
System.out.println('Moving the space with path ' + space.getPath() + ' or id ' + space.getUuid() + '\n' +
'to destination space with path ' + dest.getPath() + ' or id ' + dest.getUuid() + '\n' +
'by using the name ' + newname + '.');
getRepositoryService().update(cml);
} catch (Exception e2) {
System.err.println('Can not move the space.');
throw e2;
}
}
Since we know how to move a space we also can use the 'moveSpace' method to rename a space:
protected void renameSpace(Reference space, String newName) throws Exception
{
Reference parent = getParent(space);
moveSpace(space, parent, newName);
}
To rename a space simply means to move it with another name to the same parent space.
To get the content of a document as an array of bytes the following source code can be used:
protected byte[] getContent(Reference node) throws Exception
{
Content content = null;
System.out.println('Getting content of document with path ' + node.getPath() + ' or id ' + node.getUuid() + '.' );
try {
Content[] read = getContentService().read(new Predicate(new Reference[]{node}, STORE, null),Constants.PROP_CONTENT);
content = read[0];
System.out.println('Got ' + read.length + ' content elements.');
System.out.println('The first content element has a size of '+ content.getLength() + ' segments.');
}
catch (Exception e)
{
System.err.println('Can not get the content.');
throw e;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = ContentUtils.getContentAsInputStream(content);
byte[] buf = new byte[2048];
int size;
while ((size=in.read(buf)) != -1 ) {
out.write(buf, 0, size);
}
return out.toByteArray();
}
The following example shows how to use this method:
public byte[] downloadTitle(String artistSpaceName, String albumSpaceName, String titleName) throws Exception
{
startSession();
Reference title = new Reference(STORE, null, getCompanyHome().getPath() + '/cm:Mediaclient' + '/cm:' + normilizeNodeName(artistSpaceName) + '/cm:' + normilizeNodeName(albumSpaceName) + '/cm:' + normilizeNodeName(titleName));
byte[] content = getContent(title);
endSession();
return content;
}
Let's combine our clients to update a document by replacing it.
In my case the document is an MP3 file and has a special aspect assigned. Instead updating the documents properties directly I used the existing clients to perform this update. The following examples shows the run method of a Java bean named TitleUpload and may explain what I am meaning:
public void run() throws Exception
{
System.out.println('Uploadig file ' + titleName);
System.out.println('================================================================');
UploadClient alfresco_upload = new UploadClient();
UpdateClient alfresco_update = new UpdateClient();
DownloadClient alfresco_download = new DownloadClient();
String path = '';
if ( create )
{
path = alfresco_upload.uploadTitle(artistName,albumName,titleName,titleGenre,titleLength,data);
}
if ( !create && override )
{
alfresco_update.deleteTitle(artistName, albumName, oldTitleName);
path = alfresco_upload.uploadTitle(artistName,albumName,titleName,titleGenre,titleLength,data);
}
if ( !create && !override)
{
byte[] oldData = alfresco_download.downloadTitle(artistName, albumName, oldTitleName);
alfresco_update.deleteTitle(artistName, albumName, oldTitleName);
path = alfresco_upload.uploadTitle(artistName,albumName,titleName,titleGenre,titleLength,oldData);
}
System.out.println('The path is:' + path);
System.out.println('================================================================');
}
In summary the following cases are covered:
One node can have assigned several aspects. An out of the box available aspect is for instance 'versionable'. It adds version information to a document. I will show in this chapter how to use an user defined aspect to add additional properties to an document.
Before we can use the aspect we have to tell the Alfresco server how our aspect looks like. Therefore an XML definition of our aspect must be placed inside the server's extension folder. Here an example definition, which means the contents of the file 'MediaClientAspect.xml':
<model name='my:mediaclient' xmlns='http://www.alfresco.org/model/dictionary/1.0'>
<description>The Mediaclient Model</description>
<author></author>
<version>1.0</version>
<imports>
<import uri='http://www.alfresco.org/model/dictionary/1.0' prefix='d'/>
<import uri='http://www.alfresco.org/model/content/1.0' prefix='cm'/>
</imports>
<namespaces>
<namespace uri='my.mediaclient.model' prefix='my'/>
</namespaces>
<aspects>
<aspect name='my:mediaclientAspect'>
<title>The Ingres Mediaclient aspect</title>
<properties>
<property name='my:Ingres Mediaclient name'>
<type>d:text</type>
</property>
<property name='my:Ingres Mediaclient genre'>
<type>d:text</type>
</property>
<property name='my:Ingres Mediaclient length'>
<type>d:text</type>
</property>
<property name='my:Ingres Mediaclient album'>
<type>d:text</type>
</property>
<property name='my:Ingres Mediaclient artist'>
<type>d:text</type>
</property>
</properties>
</aspect>
</aspects>
</model>
As you can see we defined that any document with the 'mediaclientAspect' has the additional properties 'Ingres Mediaclient name', 'Ingres Mediaclient genre', 'Ingres Mediaclient length', 'Ingres Mediaclient album' and 'Ingres Mediaclient artist'.
To register this new Aspect we have to create/edit two additional files those are also placed inside the server's extension folder. The first one is the file 'custom-model-content.xml':
<beans>
<bean id='extension.dictionaryBootstrap' parent='dictionaryModelBootstrap' depends-on='dictionaryBootstrap'>
<property name='models'>
<list>
<value>alfresco/extension/MediaClientModel.xml</value>
</list>
</property>
</bean>
</beans>
The second file which must be adapted is the 'web-client-config-custom.xml':
<alfresco-config>
<config evaluator='aspect-name' condition='my:mediaclientAspect'>
<property-sheet>
<show-property name='my:Ingres Mediaclient name'/>
<show-property name='my:Ingres Mediaclient genre'/>
<show-property name='my:Ingres Mediaclient length'/>
<show-property name='my:Ingres Mediaclient album'/>
<show-property name='my:Ingres Mediaclient artist'/>
</property-sheet>
</config>
<config evaluator='string-compare' condition='Action Wizards'>
<aspects>
<aspect name='my:mediaclientAspect'/>
</aspects>
</config>
</alfresco-config>
To encapsulate the attributes of an aspect a new class named Aspect was created inside the the package community.ingres.mediaclient.alfresco. This class has the following global variables:
protected String aspect_name = '';
protected String aspect_domain = '';
protected ArrayList<String> property_names = new ArrayList<String>();
protected ArrayList<String> property_values = new ArrayList<String>();
Additionally the constructor 'public Aspect(String aspect_name, String aspect_domain, String[] property_names, String[] property_values)' and some getter, setter and helper methods are included to access the aspect's attributes.
The class MediaClientAspect is a child of Aspect. It extends the class Aspect with an own constructor and special setter and getter methods to access the MediaClientAspect's properties directly.
Here the constructor:
MediaclientAspect()
{
aspect_name = 'mediaclientAspect';
aspect_domain = '{my.mediaclient.model}';
property_names = ArrayToList(new String[]{'Ingres Mediaclient name','Ingres Mediaclient genre','Ingres Mediaclient length','Ingres Mediaclient album', 'Ingres Mediaclient artist' });
property_values = ArrayToList(new String[]{'','','','','',''});
}
You can see that the aspect's name, domain and properties are the same as we did choose in our XML definition.
Here one of the getters and and the setters those were added to the MediaClientAspect class to access the apect's properties directly:
public void setName(String name)
{
property_values.set(0, name);
}
public String getName()
{
return property_values.get(0);
}
This section explains how to set the aspect of a document during it's creation. The source code is nearly the same as in the section 'Create and upload a document' but it additionally contains the CML for adding the aspect.
protected Reference createDocument(Reference parentref, String docname, byte[] content, Aspect aspect, String mime_type) throws Exception
{
Reference document = null;
ParentReference parent = ReferenceToParent(parentref);
parent.setChildName(Constants.createQNameString(Constants.NAMESPACE_CONTENT_MODEL,normilizeNodeName(docname)));
//CML for the Create
NamedValue[] properties = new NamedValue[]{Utils.createNamedValue(Constants.PROP_NAME, docname)};
CMLCreate create = new CMLCreate('1', parent, null, null, null, Constants.TYPE_CONTENT, properties);
//CML for adding the Aspect
String[] aspect_prop_tmp = aspect.getProperty_names_as_array();
String[] aspect_val_tmp = aspect.getProperty_values_as_array();
NamedValue[] aspect_properties = new NamedValue[aspect_prop_tmp.length];
for (int i = 0; i
This was the first of two tutorials those describe how the Mediaclient demo application was developed. It contained detailed information about the clients those were implemented to communicate with the Alfresco server by focusing on Alfreso's Web Service API for Java. The second tutorial will provide further details how to integrate these 'Alfresco clients' into a JBoss Seam based web application.
Positive and negative feedback is very welcome. Please send it via email to david.maier@ingres.com !
--david.maier@ingres.com 11:48, 24 June 2009 (BST)