Getting Started

Table of Contents

3.1. Overview
3.2. Prerequisites
3.3. Eclipse Plugins v Command Line
3.4. Learning from the project sample
3.5. Setting up the sample project
3.5.1. Some handy environment settings
3.5.2. Checking the sample project out of mercurial
3.5.3. Initialize the Eclipse workspace
3.5.4. Setting up your project for importing into Eclipse
3.5.5. Importing the sample project into Eclipse
3.5.6. Running JUnit tests on the sample code
3.5.7. Building the jar file from the sample project
3.5.8. Deploying the jar file to the Nuxeo server
3.5.9. Starting the nuxeo server
3.5.10. Viewing your changes via the UI
3.5.11. Using Ant
3.6. Understanding the sample code
3.6.1. Two types of changes
3.6.2. The layout of our sample project
3.6.3. A bit about extension points
3.6.4. Declaring the 'Book' document type
3.6.5. Displaying book documents
3.6.6. Actions, tabs and behavior
3.6.7. Making book documents indexable and searchable
3.6.8. Enabling drag&drop creation (plus creating our own extension points)
3.6.9. Regulating book states
3.6.10. Workflow
3.6.11. Listening for events
3.7. Starting a new project
3.8. Using Documentation
3.9. Other IDEs: IntelliJ IDEA and NetBeans
3.9.1. IDEA
3.9.2. NetBeans

3.1. Overview

In this chapter we will show you how to develop with Nuxeo EP. It is recommended that you first read the 'Learning Nuxeo EP5 Guide' before reading this chapter. It will also provide you with a good introduction to developing with Nuxeo EP and give you a better understanding of the additional examples provided by the sample project which are discussed here.

You can think of your development task as using an IDE (such as Eclipse) to create a jar file from your source code, that can then be deployed (ie. copied) to the nuxeo server's 'plugin' directory. This new plugin (jar file) which you will create, adds functionality to the basic 'out of the box' Nuxeo EP Server that you will also download and setup. Here is a diagram that tries to show in simple terms what we would like to do:

It also explains simply what Nuxeo is. The nuxeo server is essentially an application (nuxeo.ear) that runs inside a JBOSS container. It contains an embedded H2 database that acts as the default document repository. The user interface to nuxeo is provided by a web browser or a rich client.

If you encounter any problems don't forget you can share your experience on the ecm mailing list .

3.2. Prerequisites

We assume from now on that you have the following ready-to-use environment set up on your computer:

  • Java Development Kit (JDK) 1.5.x (Nuxeo EP is known to work now with Java 6 or OpenJDK, but this is currently not supported)

  • Eclipse 3.5 (aka galileo) (J2EE edition)

  • A Nuxeo EP installation from the last release (download the installation wizard). You can also download a nightly build installer from http://www.nuxeo.org/static/snapshots/ , but keep in mind that this one may be broken, so don't erase your working setup with a nightly build: the result is not guaranteed.

  • Maven (for setting up eclipse and builds)

  • Mercurial (for checking out the source controlled version(s) of sample project used throughout this chapter)

  • A "unix-type" shell: if you are running windows, you can install cygwin

Note: The Nuxeo EP installer also installs and sets up the right version of JBoss on your computer, so you don't need to install it on your own.

For detailed instructions on how to set up all of this sofware, there is a good guide in Chapter 2 of the 'Learning Nuxeo EP5 Guide'.

3.3. Eclipse Plugins v Command Line

We will check out our sample project's source code from a remote repository using Mercurial and then import this source into Eclipse. We will use Eclipse as a development tool only here. The resulting artifact (Nuxeo projects are well described as maven artifacts - which can be thought of as just the output of a given project, for eg. a jar, a war, and ear file etc), will be built from the source we create in Eclipse, via the command line using Maven.

It is possible to configure Eclipse to use plugins that will let you run both Maven and Mercurial from within eclipse. You can set up your Eclipse to do so if you prefer, however in this chapter we will describe how to use these tools from the command line. If you prefer to use the plugins you can download them from:

3.4. Learning from the project sample

Nuxeo provides you a sample project that teaches you the main components used by Nuxeo EP by introducing the 'Book' document type, which equates to a book you'd buy in a bookstore or borrow from a library (complete with ISBN number). By following this chapter, you will learn how to:

  • declare the book document type;

  • display book documents;

  • regulate book document states;

  • make book documents indexable and searchable;

  • enable drag and drop book creation mode;

  • handle book document creation events.

3.5. Setting up the sample project

3.5.1. Some handy environment settings

Before we start you may want to set the following in your .bashrc file in your home directory:

	export MAVEN_OPTS="-Xmx512M -XX:MaxPermSize=512M"
	

It sets the amount of memory available to Maven when it is doing its stuff and will help you avoid "out of memory" errors.

3.5.2. Checking the sample project out of mercurial

Create a directory called 'workspace' in your home home directory. Change into this directory and check the sample project out of the remote repository ( http://hg.nuxeo.org/addons/nuxeo-sample-project/) by running mercurial from the command line:

$ mkdir ~/workspace

$ cd ~/workspace

$ hg clone http://hg.nuxeo.org/addons/nuxeo-sample-project/

This will create a directory in your home directory called ~/workspace/nuxeo-sample-project which contains the source code used for this lesson. There are 2 branches in the mercurial repository: 5.1 and 5.2. The 5.1 branch source has been tested against the Nuxeo 5.1.6 server, whilst the 5.2 branch has been tested against the Nuxeo 5.2.0 server.

To see what branch you are using, type "hg branches". To switch between branches you can type "hg update branchName". Eg:

		$ hg update 5.1
		

The accompanying version of JBOSS used with the 5.1 Nuxeo server is jboss 4.0.5 and with the 5.2 Nuxeo server is 4.2.3.

3.5.3. Initialize the Eclipse workspace

From the workspace directory, run the command:

$ mvn -Declipse.workspace=. eclipse:configure-workspace

This command will initialise your Eclipse workspace for you, which basically means it tells Eclipse where to find your local Maven repository by setting a variable called M2_REPO in the file: ~/workspace/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.core.prefs .Your local Maven repository (which is usually in ~/.m2/repository) will hold all the jar files that our sample project depends on. You will only need to run the Maven 'configure-workspace' command (listed above) once. If you create projects in addition to the sample project later on, they can all use this same workspace (and hence the same local maven repository).

3.5.4. Setting up your project for importing into Eclipse

Next, Maven will help us set up the sample project so that it can be readily imported into eclipse. Change into your sample project directory and run the Maven commands:

$ cd ~/workspace/nuxeo-sample-project

$ mvn eclipse:clean

$ mvn eclipse:eclipse

The 'eclipse:clean' command will clean any previous eclipse setup of your sample project (ie. cleans up all the eclipse related files so that you can start again).

The 'eclipse:eclipse' command will set up eclipse related files for your project and also download any dependencies that your project needs to compile, based on the sample project's (Maven) pom.xml file. It will store these dependencies in your local Maven repository (defined by the M2_REPO variable we described above). So the first time you run this command, it may take a while. Now when you import your project into eclipse, it should have everything it needs to compile the source.

3.5.5. Importing the sample project into Eclipse

Now that your workspace, Eclipse project files, and all the dependencies have been prepared, you can import the sample project into eclipse. Start Eclipse and select File>Import-General-Existing Projects into Workspace, click next and set the root of the workspace as: ~/workspace/nuxeo-sample-project. Hopefully it will be imported with no errors.

3.5.6. Running JUnit tests on the sample code

Our sample project has some JUnit test classes defined that enables you to run tests from within Eclipse. Expand the src/test/java branch to reveal the classes under the org.nuxeo.ecm.sample tree:

  1. TestBookLifeCycle

  2. TestBookTitleService

  3. TestBookType

  4. TestRepository

You can right click on these classes and select: Run As>JUnit Test.

3.5.7. Building the jar file from the sample project

The sample project code contains all the source in a working format that is ready to be built into a jar. Normally, you'd be creating these changes yourself. However, this has already been done for you in the sample project (we will explain what it all means in the sections that follow).

So now we are ready to create a jar file from our source code. To do this run the following Maven command from your ~/workspace/nuxeo-sample-project directory:

$ mvn clean install

If all goes well you should see the "BUILD SUCCESSFUL" message and a new jar file created in the ~/workspace/nuxeo-sample-project/target subdirectory called something like: nuxeo-project-sample-5.1.6-SNAPSHOT.jar

3.5.8. Deploying the jar file to the Nuxeo server

Now that we have created the jar file, deploying it to the nuxeo server is simply a matter of copying it to the 'plugins' directory. Assuming that the nuxeo server is located in the ~/nuxeo directory, then copy the jar as follows:

$ cp ~/workspace/nuxeo-sample-project/nuxeo-project-sample-5.1.6-SNAPSHOT.jar /nuxeo/server/default/deploy/nuxeo.ear/plugins

3.5.9. Starting the nuxeo server

We can now start the nuxeo server. After it has started, we will open up a web browser to see what our sample project has produced ((Section 3.5.10, “Viewing your changes via the UI” ).

There are 2 ways to run the nuxeo server:

3.5.9.1. Running the nuxeo server from the command line

To start the nuxeo server from the command line (assuming it is installed in ~/nuxeo):

$ cd ~/nuxeo

$ bin/run.sh

Note: you can shutdown the server from the command line by entering ^C (control-C) or by running the script "~/nuxeo/bin/shutdown.sh -S" from another shell session.

3.5.9.2. Running the nuxeo server from within Eclipse

You can also set up an instance of the nuxeo server to run within Eclipse. You do this by pointing your eclipse server to the installation of the nuxeo server on your filesystem. Defining a server configuration for the Nuxeo runtime within eclipse enables you to start and stop Nuxeo directly from within eclipse.

This can be achieved by launching the new server wizard from the servers tab (make sure you are in the J2EE view in Eclipse): Right click inside the server pane, and select New>Server. You should select a Jboss v4.0 server type, click 'Next' and provide the Nuxeo EP home directory as server home (eg. ~/nuxeo) on the next screen. Select the defaults for the remainder and then click 'Finish'.

Now double click your server so you can see the server configuration we just created. We need to change the default timeout value on the server startup, because it is likely that starting the nuxeo server will exceed the default value. You should configure a starting timeout value suitable for your computer.

Expand the 'Timeouts' section and change the default to 300 seconds for example. Don't forget to save your changes.

You may also want to click the 'open launch configuration' link in the server settings view. On the arguments tab you can change the amount of memory available to the VM to avoid PermGen out of memory errors:

	  -XX:MaxPermSize=512M
	  

Now right click on the server and select 'Start' to start the server from within eclipse.

3.5.9.3. Server Logs

You can look at your server logs to look for any errors which may occur on server startup in the following directory:

~/nuxeo/server/default/log/server.log

For example: One common error is when an instance of the server has failed to stop correctly and you try to start the server again, you will get an error like:

Port 8083 already in use

In this case you will have to kill the java process from the command line before re-starting the server:

[nuxeo ~/nuxeo] ps aux | grep java
nuxeo     9919  2.4 10.5 777428 297292 ?       Sl   15:44   3:37 /usr/lib/jvm/java-6-sun-1.6.0.16/bin/java -Dprogram.name=run.bat -Djava.endorsed.dirs=/home/nuxeo/nuxeo/bin/../lib/endorsed -Xms128m -Xmx512m -Dfile.encoding=UTF-8 -classpath /home/nuxeo/nuxeo/bin/run.jar org.jboss.Main -c default
[nuxeo ~/nuxeo]	kill -9 9919
	  

3.5.10. Viewing your changes via the UI

3.5.10.1. Logging in

Now that your server is running, open up your web browser and go to the url:

http://localhost:8080/nuxeo/

Log in nuxeo with Administrator/Administrator.

3.5.10.2. Viewing your changes

In order to see what results our new jar plugin for the 'Book' document type has produced, we first need to create a Workspace (Note: A Nuxeo workspace is different from an Eclipse workspace. To understand more about the basic Nuxeo EP user concepts, see the Nuxeo User Guide). Once you have logged in, click on 'Workspaces' and select 'Create New Workspace'. Give it a title and click 'Create'.

Now click on the workspace you have created, and select 'New Document'. The list of available document types should now contain 'Book' as one of its types. Select 'Book', and Enter a title, description and ISBN, and upload a file if you want. Click 'create'.

Congratulations, you have successfully created a new Book type ! You should now see this in your Nuxeo workspace.

3.5.11. Using Ant

Although we said above that we would use Eclipse purely for development, the sample project does come with a build.xml file that can be conveniently used within the default installation of eclipse to perform the functions of building and deploying the jar file. We have already accomplished these two tasks using Maven for the build, and a unix cp for the deploy, however we will show you how this can also be automated from within eclipse using Ant.

In order to take advantage of our Ant build.xml file, we first need to tell Ant where to find our server. We will do this by creating a build.properties file which will be used by Ant to find it. The sample project comes with a file called 'build.properties.sample' in the ~/workspace/nuxeo-sample-project root directory. We will copy this file an rename it to 'build.properties' in the same directory:

$ cp build.properties.sample build.properties

Now edit the file and change the line which contains the property: jboss.dir to point to your jboss path. Note that when using the Nuxeo EP installer, the JBOSS home directory is the same as the Nuxeo EP home directory. You should use an absolute path for safety. For example if your home is /home/nuxeo and your server is installed in ~/nuxeo:

jboss.dir=/home/nuxeo/nuxeo

Now within eclipse, select 'build.xml' in the left hand pane, then right click it and choose:

Run>External Tools>External Tools Configuration.

In the pop up window, right click on 'Ant Build' in the left hand pane, then select 'New'. This should result in the nuxeo-project-sample build.xml task appearing in the configuration. If you click on the 'Targets' tab you'll see all the tasks defined ('deploy' should be the default, which includes a 'build and copy to jboss' description). We are going to copy this configuration to make a new configuration that lets us just run the copy task which will copy our built jar file (which we've already built using Maven) into the nuxeo server plugins directory (ie. this will automate our 'deploy' which we carried out manually above by using the unix cp command).

Right click on nuxeo-project-sample build.xml in the left hand pane and choose 'Duplicate'. In the "Name" field type "nuxeo-project-sample build.xml copy jar file to plugins directory" and in the "Arguments" field, type "copy". Click 'Apply':

Now when you right click on build.xml and choose Run As> External tools configuration and select your new 'copy' configuration to Run (click the 'Run' Button), you should see the following output in your eclipse console:

Buildfile: ~/workspace/nuxeo-sample-project/build.xml 
copy:
     [copy] Copying 1 file to ~/nuxeo/server/default/deploy/nuxeo.ear/plugins
BUILD SUCCESSFUL
Total time: 204 milliseconds
        

When developing, you will also find it useful not to have to restart jboss when performing changes in xhtml pages. Since releases 5.1.5 and 5.2-M2, you can add the line facelets.REFRESH_PERIOD=2 to the nuxeo.properties file in the nuxeo.ear/config folder in your server installation (eg. ~/nuxeo/server/default/deploy/nuxeo.ear/config): pages will be refreshed within 2 seconds.

For hot deploying web sample resources in this same manner from within Eclipse, you should create a new Ant configuration like we did above for the 'copy' configuration, but use the ant target 'web' as the argument to your ant task.

3.6. Understanding the sample code

3.6.1. Two types of changes

We can distinguish between 2 types of changes in the section that follow:

  1. Configuration-only changes

  2. Configuration plus Java Code changes

Sometimes enhancing Nuxeo simply requires some configuration file changes or additions. In other words, you can do a lot with Nuxeo by simply changing or adding some configuration. This is one of the nice things about it. However there are other times we will also need to do a bit more work and add some Java Code to make some enhancements.

It's just nice to know this from the outset.

3.6.2. The layout of our sample project

Here is the project source layout:

	  nuxeo-project-sample
	  `-- src
	  |-- main
	  |   |-- java 1
	  |   |   `-- org
	  |   |       `-- nuxeo
	  |   |           `-- project
	  |   |               `-- sample
	  |   `-- resources
	  |       |-- META-INF -- MANIFEST.MF 2
	  |       |-- OSGI-INF 3
	  |       |   `-- l10n
	  |       |-- directories
	  |       |-- nuxeo.war 4
	  |       |   |-- icons
	  |       |   |-- img
	  |       |   `-- incl
	  |       |       `-- tabs
	  |       |-- schemas
	  |       |-- themes
	  |       '-- workflows
	  |
	  `-- test 5
          |-- java
          |   `-- org
          |       `-- nuxeo
          |           `-- ecm
          |               `-- sample
          `-- resources
          `-- META-INF
          `-- OSGI-INF
	      

2

The main entry point is the MANIFEST.MF file. It defines variables like Provide-Package, Require-Bundle, Nuxeo-Require and Nuxeo-Component that tell the Nuxeo EP runtime where everything is and what they depend on. They define where to load component definitions (our xml configuration files) with their associated classes and resources. Essentially when the nuxeo runtime is loading up our new jar file plugin, it looks here first to see where to find everything.

3

Component definition (xml config) files are by convention located in the OSGI-INF folder. These Nuxeo EP components mainly consist of extension point configuration (see: Section 3.6.3, “A bit about extension points”). which add the functionality we want to Nuxeo.

Note

The sample project makes an architectural choice to keep multiple xml configuration files rather than having all our configuration in a single xml file. We have chosen to group together like functions (for eg. "UI configuration, "Document type configuration") into separate xml config files. We could have chosen to put these all together in a single file. It's an architectural choice for the user in the end.

1

The extension points (configured in our xml files) may reference one or more of the sample project's specific classes. They are all defined in the src/main/java folder.

4

Any dedicated web templates or resources (such as icons etc) that our sample project needs to use, are defined in the nuxeo.war folder.Note that these resources will be written into the nuxeo.war folder at deployment time.

5

Starting and stopping the Nuxeo EP server for testing the features our sample project implements is time consuming. JUnit test classes can be found in the src/test folder.

3.6.3. A bit about extension points

In much of what follows we will be talking about extension points. A good discussion of extension points is to be found in the Learning Nuxeo EP5 Guide. But it might help to just briefly qualify what we mean here when we talk about 'extension points'.

An 'extension point' can be thought of as a point in a class where it asks for information (we also say that the class 'exposes' extension point) which will allow it then to perform some function. In a sense, the class that is exposing the extension point is a helper class that allows us to add new functionality just by giving it some extra information.

A 'contribution' to an extension point is the information that is provided to that class' extension point, that then enables the class to do its work. Contributions are usually provided by us in an xml configuration file when we want to get a Nuxeo class that exposes an extension point to do something for us with the information we give it. Below is a diagram that may help you visualize extension points:

A Bundle is equivalent to a java package like "org.nuxeo.sample" and a Component which exposes an extension point is like a Java Class (eg. "BookTitleService") or one which contributes to an extension point is like an xml file "booktitle-contrib.xml" which defines the contribution. The best way to understand this is to actually see an extension point in action. If you cant wait you can skip ahead to Section 3.6.8, “Enabling drag&drop creation (plus creating our own extension points)” in which we show how to create a class with its own extension point exposed, and then how to use it (provide a contribution to it). In any case, in every example that follows, we will be providing contributions to existing nuxeo class' exposed extension points to get them to do things for us.

3.6.4. Declaring the 'Book' document type

3.6.4.1. Objective

So away we go. To start with, we would like to declare a new Document type which represents a book (a book you can buy in a bookshop, a library book etc), that you can select and manage via our 'New Document' button on our UI:

When you click the 'New Document' button you should see our new 'Book' document type:

This new document type wasn't there before (ie. it doesn't appear in the default out-of-the-box Nuxeo installation). Our sample project created it. We can split the task of creating our new 'Book' type into 2 parts:

  1. Declaring the 'Book' type and creating the schema for this type

  2. Displaying it on the UI

In this section we will describe how to declare it and define the schema for it. In the next, we will show how to display it on the UI. A good reference document for this section, is chapter 5 of the Nuxeo Book: Schemas and Documents.

3.6.4.2. Creating the 'Book' document type

Declaring the book type and creating its schema, requires only configuration changes (ie. no Java code). The changes we will need to make are:

  1. Create our book schema (book.xsd file)

  2. Declare our new 'Book' type in core-types-contrib.xml

  3. Tell the container/Nuxeo runtime how to find it (MANIFEST.MF)

3.6.4.3. Declaring the 'Book' schema

We want our 'Book' to be composed of four fields isbn, rating, publicationDate and literals. This is the schema which defines our 'Book' type. We will declare this schema in a file called src/main/resources/books.xsd. Here is a snippet from it:

...
	  <xs:element name="isbn" type="xs:string"/>
	  <xs:element name="rating" type="xs:int"/>
	  <xs:element name="publicationDate" type="xs:date"/>
	  <xs:element name="keywords" type="bk:stringArray"/>
	  ...

We should note however, that a Document in Nuxeo is generally a composite of many schemas. If you look at the full source of the 'doctype' extension point contribution in the core-types-contrib.xml file of the next section, you'll see that the book.xsd schema we defined above, is simply one of many schemas that contribute to making up our new 'Book' document type. But we don't need to define fields like 'Title' or 'Author' in our schema, because these fields are considered standard fields that have been already well defined in already existing schemas that are available to us like the dublincore schema.

3.6.4.4. core-types-contrib.xml

Next we will create a file called src/main/resources/OSGI-INF/core-types-contrib.xml which will declare our new 'Book' type and then link it to the schema which we have just created for it. But more importantly, this core-types-contrib.xml file provides an 'extension point contribution' to the Nuxeo org.nuxeo.ecm.core.schema.TypeService. This core Nuxeo class (TypeService) exposes a number of extension points which is like the Nuxeo class saying "You can add new customized functionality at this point, by providing this Nuxeo class with the information it needs to add this functionality in for you". For example the TypeService class exposes the following extension points, for which we can provide contributions:

	 org.nuxeo.ecm.core.schema.TypeService
	  |-- doctype (declare a new Document Type)	
	  |-- schema  (declare your new Document Type's schema)
	  |-- type    (show your new Document Type on the UI)
	  |-- ...
	  |-- etc
	  |-- ...	
	

Contributions to the doctype and schema extension points, only require the addition of the configuration files we are defining here (core-types-contrib.xml and book.xsd). Once defined, the TypeService class knows how to use them to create our new 'Book' type in Nuxeo. All we have to worry about is giving the TypeService the information it needs.

Here is the corresponding XML snipset extracted from the core-types-contrib.xml file

      
	  ...
	  <!-- Declare our schema and give it a name -->
	  <extension target="org.nuxeo.ecm.core.schema.TypeService"
          point="schema">
	  <schema name="book" src="schemas/book.xsd" prefix="bk">
	  </extension>
	  ...
	  <!-- Declare our new Book type and tell it to use
	          our new book schema -->
	  <extension target="org.nuxeo.ecm.core.schema.TypeService"
	  point="doctype">
	  <doctype name="Book" extends="Document">
	  ...
	  <schema name="book" >
	  ...
	  </doctype>
	  </extension>
	  ...

How do we know what information the TypeService needs ? Fortunately all this is documented here: Extension Points Documentation (5.1) and here: Extension Points Documentation (5.2) . For example if you click on the org.nuxeo.ecm.core.schema.TypeService link, you will see all the extension points that it exposes, and how to configure these in your xml.

3.6.4.5. Adding a reference to our Manifest file

Adding a reference to our core-types-contrib.xml to our manifest tells the container and nuxeo where to find our changes to add a new 'Book' document type. Here is the relevant snippet:

	  ....
      Nuxeo-Component: OSGI-INF/core-types-contrib.xml,	  
	  ....
	  

3.6.4.6. JUnit Tests

So far we have declared the 'Book' type and schema, but all that means is that Nuxeo knows about it. As it stands, the user wont be able to see our new 'Book' type unless we explicitly add it to the UI (That's where the next section comes in). However we can still test what we have done, and in fact it is important to do so. We do this by writing some junit tests.

There are differences in the way Nuxeo version 5.1 and 5.2 handles new Document types, based on the fact that changes were made in the repository between the 2 versions.

The TestBookType.java test inherits from RepositoryOSGITestCase in 5.1 and SQLRepositoryTestCase in 5.2, which both inherit from NXRuntimeTestCase which in turn eventually inherits from JUnitTestCase.

junit.framework.TestCase
|-- ....
     |--NXRuntimeTestCase
           |--RepositoryOSGITestCase (5.1)
           |--SQLRepositoryTestCase (5.2)
	  
	  

The NXRuntimeTestCase is the "raw" testcase which does a lot of OSGI related test setup, and is primarily concerned with the startup and shutdown of the nuxeo runtime in our tests. The RepositoryOSGITestCase is used for 5.1, as it handles the JCR implementation of the repository, whereas the SQLRepositoryTestCase is used for 5.2 as it handles the VCS implementation of the repository (See Nuxeo Book chapter xxxx)

The test for our declaration of the new 'Book' type and its associated schema, is inside the testBookCreation() method within TestBookType.java, which has inline documentation to explain what each step does.

A useful thing to know is that the RepositoryOSGITestCase methods:

  1. coreSession.saveDocument(DocumentModel): saves our Book document to the session in memory

  2. coreSession.save(): saves all the session changes to the repository on disk

The session can be thought of as a series of changes that are written to it. Each change written to a session (like the coreSession.saveDocument() one) is very cheap because it is done in memory. At the end, calling the coreSession.save() writes all these changes to disk in one hit, which again helps with performance.

3.6.5. Displaying book documents

3.6.5.1. Objective

In this section we want to get our new Book document type to appear on the UI. Again, this involves only configuration changes (ie. no java coding is necessary). We will end up adding or changing the following files:

  1. ui-contrib.xml

  2. book.png

  3. layout-contrib.xml

  4. deployment_fragment.xml

  5. MANIFEST.MF

3.6.5.2. Label and Icon

Books are displayed on the screen using a specific icon and label. You can see in second figure in the Section 3.6.4.1, “Objective” above, the little 16x16 image of a book, followed by the label "Book". This is achieved by providing an extension point contribution to the TypeService's exposed extension point called "type" which is responsible for displaying both of these on the UI (see section Section 3.6.4.4, “core-types-contrib.xml” above for the listing of exposed extension points of TypeService). Note however, that instead of adding our contribution to the core-types-contrib.xml file as we did when we were declaring our Book document type, we will instead add this contribution to the ui-types-contrib.xml file. This is so that all the contributions to do with UI changes can be grouped together separately. Here is the corresponding XML snipset extracted from the ui-types-contrib.xml file

...
	  <extension target="org.nuxeo.ecm.core.schema.TypeService"
	  point="type">
	  ...
	  <type id="Book" coretype="Book">
	  ...
	  <label>Book</label>
	  <icon>/icons/book.png</icon>
	  ...
	  </type>
	  ...
	  </extension>
	  ...

The attribute id="Book" must match the "doctype name" declared in the core-types-contrib.xml declared above. The coretype="Book" attribute is now optional in 5.2, but if it is present, its value must match the id attribute's value.

Note

Test it: if you want to see the effect of the <label> value on the UI, change it from 'Book' to something else and redeploy and restart your server (ie. run a $ mvn clean install to rebuild your jar file and then copy it into the plugins directory and restart your server).

Also, we need to put our little 16x16 icon "book.png" in the src/main/resources/nuxeo/icons directory for it to be deployed correctly to the server (where it will ultimately end up in the ~/nuxeo/server/default/deploy/nuxeo.ear/nuxeo.war/icons directory).

In order to ensure that it does indeed end up there, we need to make sure we have the following line in the OSGI_INF/deployment_fragment.xml file:

	  <!-- Unzip the war template -->
        <unzip from="${bundle.fileName}" to="/">
          <include>nuxeo.war/**</include>
        </unzip>
	  

3.6.5.3. Adding a reference to our Manifest file

Adding a reference to our ui-contrib.xml to our manifest tells the container and nuxeo where to find our new additions. Here is the relevant snippet:

	  ....
      Nuxeo-Component: OSGI-INF/core-types-contrib.xml,
	  ....
	  OSGI-INF/ui-contrib.xml,	  
	  ....
	  

Note that the order of these components matters, because our ui-contrib.xml depends on the core-types-contrib.xml.

3.6.5.4. How to display our Book's fields on our UI

Remember above ( Section 3.5.10.2, “Viewing your changes”) we had to create a new Nuxeo Workspace in which to create our new Book document. The rules that determine where we can display our new Book document on the UI are actually defined in our ui-types-contrib.xml file as well.

We will make a rule that Book documents can be displayed under Nuxeo folders or workspaces. These containment rules are declared to the exposed extension point called "types" in the org.nuxeo.ecm.platform.types.TypeService class. Note that this is a different "TypeService" class than the one above (check the fully qualified path name !). Documentation for the extension points exposed by this class can be found here: org.nuxeo.ecm.platform.types.TypeService

We will define this extension point contribution in the ui-types-contrib.xml file as well because our contribution relates to UI changes. Here is the XML snipset extracted from the ui-types-contrib.xml that defines them.

  ...
	  <extension target="org.nuxeo.ecm.platform.types.TypeService"
	  point="types">
	  ...
	  <type id="Folder" coretype="Folder">
	  <subtypes>
          <type>Book</type>
	  </subtypes>
	  </type>

	  <type id="Workspace" coretype="Workspace">
	  <subtypes>
          <type>Book</type>
	  </subtypes>
	  </type>
	  ...
	  </extension>
	  ...

Again, the "coretype" attribute must contain a value that relates to one of the Nuxeo Core Types or a Type that we have created ourselves like 'Book'.

3.6.5.5. The Layout of our Book document UI

Defining which layout(s) to use

Aside from displaying the little book icon and "Book" label on the screen where our Book types are accessible, we can now proceed to the more magnanimous task of defining how the UI for creating/editing or viewing a Book document looks like. For eg: Creating a new Book:

Viewing an existing Book:

We do this by defining the <layout> template(s) to use in our ui-contrib.xml file. This is an additional contribution to the same "types" exposed extension point in the same org.nuxeo.ecm.platform.types.TypeService class as above:

  ...
	  <extension target="org.nuxeo.ecm.platform.types.TypeService"
	  point="types">
	  ...
	  <type id="Book" coretype="Book">
	  ...
	  <layouts mode="any">
          <layout>heading</layout>
          <layout>book</layout>
          <layout>file</layout>
	  </layouts>
	  ...
	  </type>
	  ...
	  </extension>
	  ...

The "layouts" section defines what <layout> templates Nuxeo will use to display your Book document type on the UI. The individual <layout>s (which we will see in the next section) define what fields are shown and how they are shown. The snippet above shows that we are using 3 layouts which Nuxeo will combine in order from top to bottom on the screen. For example in the first picture in Section 3.6.5.5, “The Layout of our Book document UI” above, the "header" <layout> is responsible for displaying the fields in the top part of the UI. You will note that these include "Title" and "Description" fields which are part of the dublincore schema of our book. The "book" <layout>, displays our "ISBN" field from our book schema. Lastly the "file" <layout> displays the "Content" section where the user can choose to upload a file.

Note

Test it: remove the two lines:

<layout>heading</layout>
<layout>file</layout>	  
	  

then rebuild your jar, redeploy to the plugins directory and restart your server.

We will define the "book" <layout> in the next section, but the "header" and "file" <layout>s come standard with Nuxeo.

Defining our custom "book" layout

In the section above we told Nuxeo that we would define a <layout> called "book" which would describe what Book fields we wanted to see on the UI. In this section we will show you what that <layout> definition looks like. We want the ISBN book field to be displayed onto our UI when the user is creating a new book or editing or viewing it content. This is achieved by defining a layout in a new file we will create called layouts-contrib.xml which will group together all our layouts. Indeed, all we are doing is contributing to the "layouts" extension point of the WebLayoutManager class. Each particular layout is defined inside this extension point. Here is the XML extracted from the layouts-contrib.xml file that registers our layout with the WebLayoutManager:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.layouts">
  <extension target="org.nuxeo.ecm.platform.forms.layout.WebLayoutManager"
    point="layouts">

	<layout name="book">

      <templates>
        <template mode="any">/layouts/layout_default_template.xhtml</template>
      </templates>

      <rows>
		<row>
		  <widget>isbn</widget>
		</row>
      </rows>

      <widget name="isbn" type="text">
        <labels>
          <label mode="any">ISBN</label>
        </labels>
        <translated>false</translated>
        <fields>
          <field>bk:isbn</field>
        </fields>

        <widgetModes>
          <mode value="create">edit</mode>
          <mode value="any">view</mode>
        </widgetModes>
      </widget>

    </layout>

  </extension>
	  ...

In a layout we define rows of widgets (elements that are seen on the UI). We have one row with the "isbn" widget.

Each widget definition (in our case we only have one widget - the isbn widget) is then provided in the <widget> section of our xml file. A widget definition contains:

A "type": (eg. "text") which defines how the widget is displayed on the UI (eg. a text entry box in edit mode or a text string in view mode).

The <labels> section defines what labels you would like to appear next to the widget. You can define a different label for each "mode". For eg. in 'edit' mode you could have 'Please enter an isbn number:' and in 'view' mode you could have 'ISBN number entered:'. In our case we have a "mode" of 'any' which means display the same label in all modes.

The "fields" section describes which fields in our schema to map the widget to. In our case we are mapping to our "book" schema's isbn field.

Lastly the "widgetMode" node tells us which modes our widget will appear in on our UI (in our case the 'edit' and 'view' modes).

3.6.5.6. MANIFEST.MF

Don't forget to add entries into your MANIFEST.MF file to reference the new files you have created:

	  ....
      Nuxeo-Component: OSGI-INF/core-types-contrib.xml,
	  OSGI-INF/ui-contrib.xml,	  
      OSGI-INF/layouts-contrib.xml,
	  ....
	  

3.6.5.7. Unit Tests

It is possible to install Selenium to test our new UI changes as it is difficult to write JUnit tests to do so. For now, we suggest just starting up the Nuxeo server and going in and verifying that your changes work by creating a new Book document and seeing if you can view it.

3.6.5.8. Summary

In this section we described how to get the our Book document's fields to be displayed on the screen, and how to display them.

3.6.6. Actions, tabs and behavior

3.6.6.1. Objective

We can add tabs to the different views a user has in the Nuxeo UI and associate different behaviors with each tab.

In this section we will describe how to add 2 tabs:

  1. A "Books" tab to the workspace view: When you click on a workspace, you will see a number of tabs. We will insert a "Books" tab that lets you do certain book associated tasks, like viewing all the books in the folder and searching for all books with a particular keyword (see: Section 3.6.7, “Making book documents indexable and searchable” ).

    Note

    Searching on a keyword assumes these keywords have been setup somewhere and somehow. We will just assume this in this section. We show you how to set up the keywords in Section 3.6.11.5, “Directories and Vocabularies”

  2. A "Book" tab to the book view: When you click on a book item that you have already created, you will also see another tab called 'Book' which allows you to edit some of the Book's properties and attach Keywords to the book (these are the keywords we can search on in the "Books" tab above).

It is the first time also that we will be using some Java code in addition to configuration changes/additions. The java code will help us implement some of the custom behavior we want. However we will not focus on this, as it is important to understand that you can do a lot with just the configuration changes that we will explain.

A good reference for this section is Section 7.1, “Introduction”

3.6.6.2. Configuration

Our configuration happens in a file called actions-contrib.xml. In this file we provide a contribution to the extension points called "actions" and "filters" on the ActionService class. Here is the actions-contrib.xml file:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.actions">

  <extension target="org.nuxeo.ecm.platform.actions.ActionService" point="actions">
    <action id="tab_book_view"
      link="/incl/tabs/book_view.xhtml" enabled="true"
      label="Book" icon="/icons/file.gif" order="9">
      <category>VIEW_ACTION_LIST</category>
      <filter id="book_filter">
      </filter>
    </action>

    <action id="tab_folder_books_list"
      link="/incl/tabs/folder_books_view.xhtml" enabled="true"
      label="Books" icon="/icons/file.gif" order="15">
      <category>VIEW_ACTION_LIST</category>
      <filter id="workspace_books_filter">
        <rule grant="true">
          <type>Folder</type>
          <type>Workspace</type>
        </rule>
      </filter>
    </action>
  </extension>

  <extension target="org.nuxeo.ecm.platform.actions.ActionService" point="filters">
    <filter id="book_filter" append="true">
      <rule grant="true">
        <type>Book</type>
      </rule>
    </filter>
  </extension>

</component>	  
	  

The first thing to note are the 2 extension point contributions "actions" and "filters".

The "filters" extension point: is where you can add a "filter". Filters are generally are used to change the access rights to a tab. We name and create filters in this section when our filter is particularly complex (and we then want to separate it, define it, and call it by name from the "actions" extension point), or when we just want to re-use a particular filter, and therefore we can again reference it by its name.

Our example filter in actions-contrib.xml called "book_filter", is saying that if a Document is a child of the "Book" type, then grant access (visibility) to this tab. This example shows how filters can be used to manage access rights to tabs. It is convenient, as the user does not have to write Java code to deal with permissions.

The "actions" extension point: is used to configure what action to take when it is selected. The action id names the action. If you hover your mouse over the "Book" tab in the Book view (select a book that you have already created and then hover your mouse over the "Book" tab on the next screen), you'll see that the url contains the action id.

The "link" attribute identifies which action to call when your tab is selected. The link here specifies a JSF page called book_view.xhtml. We will describe this JSF page in brief in a following section. For now, all we need to know is that clicking the "Book" tab will request this action. The "enabled" attribute is also important, because it defines whether or not a tab will be visible to the user or not. So we can turn the visibility of tabs on and off with this attribute which is quite useful. The "label" is the text that will appear on the tab, and the "icon" is the icon associated with the tab.

The "category" node defines a pre-defined place on the page on which to display your tab. Common values are:

  1. VIEW_ACTION_LIST: used for tabs

  2. USER_SERVICES: used to display actions in the top right corner of every page

  3. SUBVIEW_UPPER_LIST: used to display actions just below a document tabs listing

See Section 7.2.2.2, “Manage category to display an action at the right place” for more information.

The "filter" node is also important. You can define either an inline filter (as has been done with the "tab_folder_books_list" action), or you can just call a named filter (which is what the "tab_book_view" action does). The "filter" nodes define what filters to associated with this action. For the "tab_book_view" action, our filter allows the user access to the tab only if the document is a child of the Book type.

3.6.6.3. JSF/Java

The actions-contrib.xml file above, calls the book_view.xhtml and folder_books_view.xhtml jsf pages as its actions. (Note that tab's templates are defined into the folder nuxeo.war/inc/tabs.) Without going into detail, you can see from the snippet below that the jsf looks a lot like html, but with the use of jsf tags like <h:outputLabel value="keywords"> instead of html.

Note also that these templates get their model using the BookManagerBean which implements book behaviours. You'll see that anything with the "#{}" around it is a call to the BookManager.java class.

3.6.6.4. Summary

In this section we saw how to add tabs with access permissions and behaviors using contributions to the "actions" and "filters" extension points on the ActionService class. Don't forget to add a line to your MANIFEST.MF to include the actions-contrib.xml file. The next section includes details of how the "Books" tab page is used.

3.6.7. Making book documents indexable and searchable

3.6.7.1. Objective

We want to allow our book to be searchable via the Nuxeo UI. There is a little 'search box' in the top left corner of our Nuxeo UI. You don't need to do anything specific to have this search, search any of your book documents.

However, in the section below, we will show how to do a customized search that you can set up 'programatically'. To do this we need to do 4 things:

  1. Tell Nuxeo which fields from our Book schema to index. Indexing means that these fields can be searched when a user does a search. Note however, that this first step is only necessary in Nuxeo 5.1. In Nuxeo 5.2 you no longer have to tell Nuxeo which fields to index in your Document schema, because it will index all fields by default.

  2. We also need to tell Nuxeo how to search. For example, should we search with 'Starts With' or 'Contains' and which fields should we search.

  3. Then we need to tell Nuxeo what Java class will be doing the searching for us.

  4. Finally we will need a page on the UI to show our search options and display our search results.

A good reference for this section is Section 11.1, “Introduction”.

3.6.7.2. Indexing

Our first step is to tell Nuxeo which fields in our Book schema we want indexed. These indexed fields will be the ones that can be searched. This step is only necessary in Nuxeo 5.1. In 5.2, all fields from our Book schema will be searchable by default.

To tell Nuxeo which fields to index, we register an extension point contribution to the extension point called "indexableDocType" on the SearchServiceImpl class. We do this in a file called the search-contrib.xml file. Don't forget that we need a reference to this in our MANIFEST.MF as it is a new file. Here is the contents of the search-contrib.xml:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.search">

  <extension
    target="org.nuxeo.ecm.core.search.service.SearchServiceImpl"
    point="indexableDocType">
    <indexableDocType indexAllSchemas="true" name="Book" />
  </extension>

</component>	  
	  

As you can see we are telling the SearchServiceImpl class to index all fields on all schemas that make up our Book document. So all fields of our book will be searchable.

3.6.7.3. Query Model

Next we need to tell Nuxeo how we want to search. This is called our 'Query Model'. The query model lets us define our search in an 'SQL-like' way so that we can specify our search parameters. An example in pseudo-code might be:

"select all Book documents where the user's input string matches one of the book's keywords.".

To do this, we register a 'Query Model' with the "model" extension point of the QueryModelService. We can do this by creating a new file called querymodel-contrib.xml and specifying the search "pattern":

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.querymodels">

  <extension
    target="org.nuxeo.ecm.core.search.api.client.querymodel.QueryModelService"
    point="model">

    <queryModel name="BOOK">
      <pattern>
        SELECT * FROM Document WHERE ecm:primaryType = 'Book' AND ecm:path STARTSWITH ? AND
        bk:keywords = ?
      </pattern>
         <sortable value="true" defaultSortColumn="dc:title"
        defaultSortAscending="true" />
    </queryModel>

  </extension>


</component>	  
	  

The query language that we use in the "pattern" attribute is called NXQL and more documentation about it can be found here: Section 11.3.1, “Fields and literals” .

3.6.7.4. Tell nuxeo about our search class

After we have setup our query model, we need to register the Java Class that will be invoking this query model (and dealing with the results of the query), with the "model" extension point of the ResultsProviderService. Again we do this in a new file called: resultsprovider-contrib.xml. Here are the contents of that file:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.resultsproviders">

  <extension
    target="org.nuxeo.ecm.webapp.pagination.ResultsProviderService"
    point="model">

    <resultsProvider name="BOOK" farm="bookResultsProviderFarm" />

  </extension>
</component>
      

Notice that the "farm" attribute points to the src/main/java/BookResultsProvider.java class that we have written. This class will be the class that retrieves the query model we setup above and uses it to perform our customized search.

3.6.7.5. Displaying our Search Results

Finally having set up our search, we need a page to display the search options and show the results of our search on the UI. We do both these things on the same page: Books are both searched for, and listed, in the book's tab using the resultsProviderCache which we can access in the folder_books_view.xhtml JSF page we setup above: Section 3.6.6.2, “Configuration”

...
	  <nxu:methodResult name="provider"
          value="#{resultsProvidersCache.get('BOOK')}">

	  <h:form>
          <ui:decorate template="/pagination/pageNavigationControls.xhtml">
          <ui:param name="provider" value="#{provider}" />
          </ui:decorate>
	  </h:form>

	  <h:dataTable var="bookinfo" value="#{provider.currentPage}"
	  ...
	  </h:dataTable>
	  ...
	  

The following screen shot shows the results of our handiwork:

Note: you can setup your books to appear in this screen like this by selecting a book that you have already created, going to the "Book" tab and entering in Keyword and Note for your book. Then clicking on a Keyword searches for all the books that contain that keyword if it was entered against them on the "Book" tab.

3.6.7.6. JUnit Tests

3.6.8. Enabling drag&drop creation (plus creating our own extension points)

3.6.8.1. Objective

Nuxeo makes it easy to enable drag and drop so that dragging a file onto a workspace will automatically create a Book document. In this section we will show you how to enable drag and drop in Nuxeo.

Note

You will need to install the nuxeo drag and drop plugins for Firefox or Internet Explorer for drag and drop to work. You can download these at:

As by-product of explaining how to enable drag and drop in Nuxeo in this section, we will see how to create our own Service and create an extension point on that service. So far we have been only providing contributions to existing extension points in services within Nuxeo. But this section gives us the opportunity to

  1. Create our own service with its own exposed extension point

  2. Provide a contribution to our own exposed extension point

3.6.8.2. Notification for drag and drop events

But first, before we create our own service with its own extension point, let us again begin by providing an extension point contribution to an existing Nuxeo service. By providing an extension point contribution to the "plugins" extension point on the FileManagerService class, we can have this class notify us when a drag and drop event occurs. We will put this contribution in the filemanager-contrib.xml file:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.filemanager">

  <extension
    target="org.nuxeo.ecm.platform.filemanager.service.FileManagerService"
    point="plugins">
    <plugin name="book_plugin"
      class="org.nuxeo.project.sample.BookFileManagerPlugin">
      <filter>image/gif</filter>
      <filter>image/jpeg</filter>
    </plugin>
  </extension>

</component>	  
	  

The BookFileManagerPlugin has requested that the FileManagerService call the BookFileManagerPlugin class whenever a gif or jpeg upload (drag and drop) occurs. The notification takes the form of calling the BookFileManagerPlugin's create() method.

3.6.8.3. Registering our own Service

Now we get to register our own service with its very own extension point exposed. This service is called by our BookFileManagerPlugin when it has been notified by the Nuxeo FileManagerService that a ".gif" or ".jpeg" has been dragged and dropped onto the UI. The BookFileManagerPlugin will use this Service to help it create a new Book document from the uploaded file.

We will register the service in the booktitle-service-contrib.xml file as follows:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.BookTitleService">

  <implementation class="org.nuxeo.project.sample.BookTitleServiceImpl" />

  <service>
    <provide interface="org.nuxeo.project.sample.BookTitleService" />
  </service>

  <extension-point name="title">
    <object class="org.nuxeo.project.sample.BookTitleDescriptor" />
  </extension-point>

</component>
	  

In creating this xml configuration, we are defining a new nuxeo <component> called org.nuxeo.project.sample.BookTitleService which defines a new service called BookTitleService whose <implementation> class is the org.nuxeo.project.sample.BookTitleServiceImpl class. Furthermore, we define an <extension> point on our component called "title", which we will map to a java class called BookTitleDescriptor.

Ok. Now when we define our extension point contribution, which we will do in the booktitle-contrib.xml file, the fields in this xml file should map to the annotations in our BookTitleDescriptor. This is essentially how you find out what information your extension point requires:

booktitle-contrib.xmlBookTitleDescriptor.java
<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.booktitle">
  <extension 
         target="org.nuxeo.project.sample.BookTitleService" 
         point="title">
    <configuration>
      <addInitialCapital>true</addInitialCapital>
      <removeExtension>true</removeExtension>
      <addComment>(automatic)</addComment>
    </configuration>
  </extension>
</component>
package org.nuxeo.project.sample;

import org.nuxeo.common.xmap.annotation.XNode;
import org.nuxeo.common.xmap.annotation.XObject;

@XObject("configuration")
public class BookTitleDescriptor {

  @XNode("addInitialCapital")
  protected boolean addInitialCap;

  @XNode("addComment")
  private String addComment;

  public String getAddComment() {
	return addComment;
  }

  private boolean remove;

  @XNode("removeExtension")
  protected void setRemoveExtension(boolean remove) {
	this.remove = remove;
  }

  public boolean getRemoveExtension() {
	return remove;
  }
}

Table 3.1. Extension Point


So the flow of events when a ".gif" or ".jpeg" document is dragged onto the Nuxeo UI is as follows:

The FileManagerService notifies the BookFileManagerPlugin by calling its create() method. To do its work of creating a new Book document, the BookFileManagerPlugin class calls the BookTitleService's correctTitle(title) method, passing it the name of the file that was uploaded. The BookTitleService then retrieves the values provided by the "title" extension point which are set in the booktitle-contrib.xml file, and uses these to correct the title (ie. Change the first letter to a capital, removes the file extension - eg. ".gif"), and adds a comment, before creating a new Book document with this title and saving it to the repository.

FileManagerService
  |-- BookFileManagerPlugin.create()
        |--BookTitleServiceImpl.correctTitle(title)
		      |-- BookTitleDescriptor (get values from booktitle-contrib.xml config)
			  |-- create the title base on the values
        |-- create the new Book with this title in the repository
			     
	  

3.6.8.4. JUnit Tests

The Sample Project contains a class called TestBookTitleService which tests whether our new configuration and java changes for creating our new BookTitleService service works.

3.6.8.5. Differences between 5.1 and 5.2

If you look at the source code of the BookFileManagerPlugin in the 5.1 vs 5.2 branch, you will notice that in 5.1 it extends AbstractPlugin whereas in 5.2 it extends AbstractFileImporter. The AbstractPlugin is deprecated in 5.2.

The BookManager and BookMangerBean classes have import(s) changed from 5.1 to 5.2:

	  import org.jboss.seam.annotations.WebRemote; (5.1)
	  import org.jboss.seam.annotations.remoting.WebRemote; (5.2)
	  

and

	  import org.jboss.seam.core.FacesMessages; (5.1)
	  import org.jboss.seam.faces.FacesMessages; (5.2)
	  

As these have changed packages between the jboss versions used between 5.1 (4.0.5) and 5.2 (4.2.3).

The BookManager interface has been changed to return a DocumentModelList instead of a ResultSet

	  public ResultSet getSearchResults() throws Exception; // (5.1)
	  public DocumentModelList getSearchResults() throws Exception; //(5.2)
	  

and the BookManagerBean which implements the BookManager interface, has had its code updated accordingly, and also been made Serializable. The reason is that SearchService is not used anymore. Now queries are executed with the method query() from the CoreSession, and this new method returns a DocumentModelList instead of a ResultSet.

The BookResultsProviderFarm has also been made Serializable in 5.2, and had its call to getResultsProvider() updated.

3.6.9. Regulating book states

3.6.9.1. Objective

We want to make Books be regulated by the standard life cycle ( project, approved, obsolete, deleted states). As defined in the Chapter 4 of the Nuxeo User's Guide "The evolution of a document, each time contributors edit it, constitutes its life cycle ... To change the life cycle state of a document, you need to submit it to a workflow.". You can find out more about life cycles and workflows from the user's perspective by reading Chapter 4 of the Nuxeo User's Guide .

In this section we will show you just how to register a document to the default life cycle states. To get it to actually transition between these states will be discussed in a separate section on Section 3.6.10, “Workflow” .

3.6.9.2. How to

We can get a Book to follow the Nuxeo standard lifecycle by declaring to the "types" extension point of the LifeCycle Service that Books follow the "default" life cycle. Here is the lifecycle-contrib.xml file

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.lifecycles">

  <extension target="org.nuxeo.ecm.core.lifecycle.LifeCycleService"
    point="types">
    <types>
      <type name="Book">default</type>
    </types>
  </extension>

</component>
	  

Getting your new Book document type to follow the standard Nuxeo lifecycle is as simple as that. Don't forget that a reference to the lifecycle-contrib.xml file also needs to go in the MANIFEST.MF file.

3.6.9.3. JUnit Tests

We can also write a JUnit Test to test our life cycle contribution. If you look in the sample project's src/test/java directory you'll see a test class called TestBookLifeCycle.java. It is self documented for you to follow the steps.

3.6.10. Workflow

3.6.10.1. Objective

3.6.10.2. Workflow in 5.1

TO DO (code not yet implemented in 5.1 branch either)

3.6.10.3. Workflow in 5.2

TO DO (code exists in 5.2 branch)

3.6.11. Listening for events

3.6.11.1. Objective

Event Listeners are java classes that get notified by nuxeo when a particular type of event (that they are "listening" for) occurs. In this section we will show you how to create your own Event Listener java classes to listen for particular events and then to do something useful when the event occurs.

3.6.11.2. Registering event listeners

Event Listeners are registered as contributions to the extension point "listener" on the EventServiceComponent class (in 5.2) or on the CoreEventListenerService class (in 5.1). In the sample project we have registered these event listeners in the event-listener-contrib.xml file. These contributions are basically registering a java class that you will write (our "EventListener" classes), that will get notified when a particular event occurs. The event or events that your customized class listens for can be specified by the <event> tag in the xml. Here is the listing of event-listener-contrib.xml:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.event-listeners">
	<extension target="org.nuxeo.ecm.core.event.EventServiceComponent"
		point="listener">
		<listener name="book_create" async="false" postCommit="false"
			class="org.nuxeo.project.sample.BookEventListener">
			<event>emptyDocumentModelCreated</event>
		</listener>
		<listener name="book_update_isbn" async="false" postCommit="false"
			class="org.nuxeo.project.sample.BookISBNEventListener" order="157">
			<event>documentCreated</event>
			<event>documentModified</event>
		</listener>
	</extension>
</component>	   
	   

As we can see above are 2 event listeners in the sample project:

  1. BookEventListener: which listens for an 'emptyDocumentModelCreated' event

  2. BookISBNEventListener: which listens for both documentCreated and documentModified events

3.6.11.3. BookEventListener

Our BookEventListener class will get notified whenever a emptyDocumentModelCreated event occurs. In other words, when the user clicks the 'New Document' button, it will get notified. The class itself is very simple. It implements the EventListener interface, which means that we must implement a handleEvent(Event event) method in our event listener, which is passed the event that has occurred:

	  public void handleEvent(Event event) throws ClientException {
        EventContext ctx = event.getContext();
        if (ctx instanceof DocumentEventContext) {
            DocumentEventContext docCtx = (DocumentEventContext) ctx;
            DocumentModel doc = docCtx.getSourceDocument();
            if (doc != null) {
                String type = doc.getType();
                if ("Book".equals(type)) {
                    process(doc);
                }
            }
        }
	  

This method checks to see if our Document type is a 'Book' and if it is, it calls the process(doc) method (not shown), passing it the Book document, which does all the work of creating some default values for the Book's 'Title' and 'Description' fields. The user sees these as 'Sample Book' and the '(Created on 2009-01-01)' in the editable Title and Description fields respectively on the screen where a new Book is being created. The user can overwrite these values if they wish.

3.6.11.4. BookISBNEventListener

The BookISBNEventListener class listens for documentCreated and documentModified events. When it is notified that one of these events has occurred, once again its handleEvent() method is called. After checking that the event applies only to Book document types, it calls its process() method do deal with these events.

It is at this point that we can see that it that we need to digress a little, to explain what is happening next. That is because it starts doing things with a DirectoryService and a Vocabulary.

3.6.11.5. Directories and Vocabularies

The sample project defines an extension contribution to the SQLDirectoryFactory class called "directories" in the file directories-contrib.xml whose contents are listed below:

<?xml version="1.0"?>
<component name="org.nuxeo.project.sample.directories">
  <extension target="org.nuxeo.ecm.directory.sql.SQLDirectoryFactory"
    point="directories">
    <directory name="book_keywords">
      <schema>vocabulary</schema>
      <idField>id</idField>
      <dataSource>java:/nxsqldirectory</dataSource>
      <table>book_keywords</table>
      <!-- one of "never", "on_missing_columns" or "always" -->
      <createTablePolicy>on_missing_columns</createTablePolicy>
      <dataFile>directories/book_keywords.csv</dataFile>
    </directory>
  </extension>
</component>
	  

In Nuxeo, a Directory is a programmatic abstraction that let's us deal with any relational database, regardless of the actual underlying implementation (some examples might be certain implementations of ldap, databases, csv files etc). The chapter on Directories and Vocabularies in the nuxeo book, contains a convenient diagram in the Introduction section which diagrams this abstraction. A The "directories" extension contribution contained in the directories-contrib.xml file shown above, defines a new directory called "book_keywords". This directory is based on the <vocabulary> schema, which is a core nuxeo schema and can be found in the vocabulary.xsd file in the ~/nuxeo/server/default/data/NXRuntime/schemas/ directory under your server installation. If you look at this schema you'll see it contains 4 fields: id, label, obsolete and ordering. The <id> xml node in the directories-contrib.xml defines which of these 4 fields is the key field.

The other xml nodes define the other aspects of the book_keywords directory that we are defining. The <dataFile> node defines that we will keep the initial data that will populate this directory in the book_keywords.csv file. If you browse to that file in the sample project you'll see its contents:

id, label, obsolete
"1","Novel",0
"2","Bestseller",0
"3","Academic",0	  
	  

The <dataSource> node points to the actual implementation of our directory and where our data will be really stored. In our case we are using the default nuxeo sql data source and naming the table that holds our data in this dataSource <table>book_keywords</table>. Finally the <createTablePolicy> node, takes a value of on_missing_columns, which means that nuxeo will load the contents of your book_keywords if your book_keywords schema is updated. Thus when the server is first started, and no data exists in the table, the book keywords will be loaded in. Then if the server is restarted, no changes will be made to the database unless the schema has been changed.

So we have defined a Directory that will hold our book keywords that we get from our book_keywords.csv file. What do we then do with this Directory ? We can see what happens when we look at the process() method of BookISBNEventListener. Whenever a document is created or modified, our process() method gets a DirectoryService to our "book_keywords" directory. It then matches the value of the ISBN field of our book against the value of the id column of our book_keywords data (the call to dir.getEntry() will use the value of the key field 'id' defined in our book_keywords directory to match against).

	  dir = dirService.open("book_keywords");
      DocumentModel entry = dir.getEntry(isbn);
	  

If there is no match (the entry returned is null), it will create a new entry in our directory with the isbn as the "id" and the title as the "label". The mapping between our Book and our book_keywords fields is shown in the table below:

Book (Document) field book_keywords (Directory) field
isbn id
title label

Table 3.2. Book v Book Keywords mapping


If you look at the rest of the code in the process() method of BookISBNEventListener, you'll see that if there is a match between the isbn of the book and the id of the book_keywords, it will update the label of the matching book_keyword entry with the title of the matching book.

So that explains what BookISBNEventListener does, and what all the Directory/Vocabulary code in the sample project is about. The page that actually uses this code is the search facility of the 'Books' tab described in the Section 3.6.6, “Actions, tabs and behavior ” section above.

3.6.11.6. JUnit Tests

TO DO

3.7. Starting a new project

The goal of the nuxeo-archetype-start template is to setup a Nuxeo EP plugin development environment. The template provides: a maven layout for sources, tests and dependencies, an Ant target for deployment. It also customizes the web application a litte bit.

Start the “new maven project” wizard from the menu File > New > Maven project. Enable your eclipse for using Nuxeo EP's by addind a remote archetypes catalog using the configure dialog. The URL to be used is as follow http://maven.nuxeo.org/nuxeo-release/. Select the archetype type nuxeo-archetype-start and the nuxeo's version you want your project based. Set the artifact parameters according to your organisation rules.

3.8. Using Documentation

  • The Nuxeo Book is getting to be the most complete source of information around Nuxeo EP, both for beginners and advanced developer. It is a good start.

  • The extension point documentation is also very useful: although you may find it rough, it is the best way to evaluate the Nuxeo extensibility potential, and one should always start with a quick look around all the extension points, to "think Nuxeo" before starting a new project, and not reinventing the wheel.

  • The wiki: we try to reference all the documentation from the wiki welcome page, and you will find tricks, howtos, etc. If you want to have a writer account to help update the content, ask on the Nuxeo's mailing list.

3.9. Other IDEs: IntelliJ IDEA and NetBeans

3.9.1. IDEA

IntelliJ IDEA from Jetbrains is a very lovable IDE for Java that has many fans in the Java developers community. It is unfortunately not open source.

To start using IDEA for coding on the Nuxeo project, you just need to type mvn idea:idea from your top-level source directory: Maven will download all the dependencies, then generate the configuration files needed by IDEA and you will be able open nuxeo-ecm.ipr from IDEA.

Note

At the time of this writing, IDEA will report some (spurious) compilation failures and you won't be able to compile the whole application from IDEA. You can still use IDEA with the current configuration to write Java code ("with pleasure"), and use Ant and/or Maven to build/deploy the application.

3.9.2. NetBeans

NetBeans is an open source IDE from Sun.

If you're using NetBeans 6 or later, you will be able to download a Maven 2 support plugin from NetBean's "Update Center", and with it, start coding on Nuxeo EP very quickly.