Table of Contents
Objective: To learn about what triggers events to be fired in the Nuxeo system and how to take actions in Java code to handle these events. Learn how to test Java code that is part of a bundle inside Eclipse.
In historic events, the so-called great men are labels giving names to events, and like labels they have but the smallest connection with the event itself. -- L. Tolstoy, Russian novelist (1828-1910)
If you have any comments, questions, or general-purpose harassment you would like give us about this book, then please use the comment form at the bottom of each page! We promise that we will try to incorporate any feedback you give (minus the profanity, of course), will respond to your questions, and credit you appropriately.
In the lessons to this point, we have not required the reader to write Java code. The reasons for this "XML first" approach (with apologies to Winston Churchill) were two fold. First, it allowed us to show many features and functions of the platform that you can access without Java code. Second, it allowed the introduction of many key concepts with minimal "overhead" during the explanation. The reader should now be quite familiar with concepts like manifests, extension points, schemas, and document types so detailed explanations will not be necessary as we use these concepts in the Java code.
The editor is not convinced that the construction of sets of XML files that correctly "implement" desired functionality, such as we have done with the Upcoming document type, is not "coding." Many authors would argue that the ability to configure Nuxeo via XML files, such as the reader has done to this point, shows that Nuxeo's feature set (and power) can be used "without coding," but this phrase has been deliberately omitted in this book.
Up to this point, we have encouraged the reader to use Eclipse
only as a text editor-for the XML files-and to use Maven to build the
bundles. Again, this decision was made to simplify the explanations
needed. Now, the rubber meets the road in the battle for supremency of
the build tools. As before, please check out the project named
lesson-events
from the SVN repository. Again, you
should use maven from the command line to construct the eclipse project
files with mvn eclipse:eclipse. This time, however,
you will also need to a script that is suppied with the lesson like
this:
/nuxeo/workspace/lesson-events$ mvn eclipse:clean [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'eclipse'. [INFO] ------------------------------------------------------------------------ [INFO] Building Events [INFO] task-segment: [eclipse:clean] [INFO] ------------------------------------------------------------------------ [INFO] [eclipse:clean] [INFO] Deleting file: .project [INFO] Deleting file: .classpath [INFO] Deleting file: .wtpmodules [INFO] Deleting file: .component [INFO] Deleting file: org.eclipse.wst.common.component [INFO] Deleting file: org.eclipse.wst.common.project.facet.core.xml [INFO] Deleting file: org.eclipse.jdt.core.prefs [INFO] Deleting directory: .settings [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1 second [INFO] Finished at: Fri Feb 27 12:26:40 CET 2009 [INFO] Final Memory: 14M/340M [INFO] ------------------------------------------------------------------------ /nuxeo/workspace/lesson-events$ $ mvn eclipse:eclipse [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'eclipse'. [INFO] ------------------------------------------------------------------------ [INFO] Building Events [INFO] task-segment: [eclipse:eclipse] [INFO] ------------------------------------------------------------------------ [INFO] Preparing eclipse:eclipse [INFO] [buildnumber:create {execution: default}] [INFO] Storing buildNumber: 20090227-122648 at timestamp: 1235734008892 [INFO] [apt:execute {execution: generate-bindings}] [INFO] [nuxeo:eclipse-version {execution: eclipsize-version}] [INFO] eclipseVersion:0.0.1.0 [INFO] [eclipse:eclipse] [INFO] Using source status cache: /nuxeo/workspace/lesson-events/target/mvn-eclipse-cache.properties [INFO] Wrote settings to /nuxeo/workspace/lesson-events/.settings/org.eclipse.jdt.core.prefs [INFO] Wrote Eclipse project for "lesson-events" to /nuxeo/workspace/lesson-events. [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 6 seconds [INFO] Finished at: Fri Feb 27 12:26:53 CET 2009 [INFO] Final Memory: 45M/410M [INFO] ------------------------------------------------------------------------ /nuxeo/workspace/lesson-events$ ./fixeclipse Fixing eclipse classpath to use bin output directory instead of target... ./.classpath Done. Replacing missing or badly generated files with .*.ok files... Done.
If you read the listing above, you will notice that it starts not
with mvn eclipse:eclipse, but with mvn
eclipse:clean. This invocation of maven insures that all
previous changes that you might have made to the eclipse configuration
for this project are deleted (cleaned). Then we use mvn
eclipse:eclipse to create the necessary files for Eclipse.
Finally, we use ./fixeclipse to "fix" some things
about the interaction between eclipse and maven. If you look at the
output of the fixeclipse script, you will it changes the Eclipse output
directory to bin
, instead of the maven standard one
of target
. If you were to not do this, you will
find that eclipse and maven will interact badly with one another.
In a previous lesson when we described how to set up your
development environment, we warned against using any of the "maven
plugins" within eclipse. We did this because a common one, m2eclipse,
attempts to "merge" all the output directories into
target
and the merging does not work well with
Nuxeo + Eclipse. We know because some of the authors use it! Now that
you have run the fixeclipse script, you actually can use the m2eclipse
successfully. m2eclipse, however, may not have the result you
anticipated since building within Eclipse now sends its output to bin
and not to target, so you must explicitly invoke Maven from the
Eclipse interface to do a build or install. We will continue to assume
in the text that you are using maven from the command line.
We have also had some feedback that other Maven plugins for Eclipse, notably Maven4MyEclipse from Genuitec, does not have any of the problems we have experienced, so it is likely to function properly without the use of the fixeclipse script.
You should now open the project in Eclipse and select the project name then choose
and then . You will see a dialog like this:There are several critical things to note in this screen shot.
First, the default output folder (at the bottom) is shown to be
lesson-events/bin/main
and this is the change that
was made by fixeclipse. Further, there are now five directories in the
source path, including
lesson-events/src/main/resources
and
lesson-events/src/test/resources
(not
pictured).
If you do not have these two directories in your source path in eclipse (the Source tab above), your bundle(s) will be unavailable inside Eclipse! The contents of these directories-with the exception of Java code-is copied directly into the output directory, including all directories. Java code is compiled first, then the resulting class files are copied into the output directory. Thus the name Source is actually a bit deceptive since Java source code is the only thing not copied wholesale into the resulting classpath of your application!
Besides the two directories we just mentioned, there are two
directories that will actually contain Java source code,
lesson-events/src/main/java
and
lesson-events/src/test/java
. These two directories
contain the java source code for your bundle, and the java source code
for testing your bundle, respectively. There is a final directory,
lesson-events/target/generated
, that is at the
bottom of this list that you should remove from your source path by
clicking on the name of the directory in the dialog above then clicking
on Remove. This directory is, as you would expect, the output directory
of some tools used in Nuxeo generate source code. Since we are not using
these, you can remove this item for now; it is probably listed as
"missing" in your display anyway.
If you switch to the Libraries tab, you will get an eyefull! This shows you the real purpose of the mvn eclipse:eclipse command, to generate a list of libraries needed by the program and place them all into eclipse:
The error shown at the top of this dialog means that the snapshot
was taken before I followed the directions above to remove
target/generated
subdirectory from the list of
directories in the tab!
This list is quite extensive and it is only those libraries need
for this fairly simple lesson! The variable M2_REPO
that you see above on each line evaluates to your current home
directory, say /home/ismith
, and then to the
.m2/repository
directory within that. To the
extreme right of the list, not shown above, Eclipse will display the
full path used for each item in this list.
Using the skeleton provided to you, open up the test directory for
java source code (src/test/java
), the package
org.nuxeo.upcoming.test, and select the file
EventTest.java
. The top of this When we warned you in
a previous lesson to not use any of ef is shown in the following
capture:
Depending on exactly how you have your editor preferences, you may see different colors, no line numbers, or no print margin line at the 80th column.
In Java code in this book, we use two distinct types of comments.
The "//" type indicate commentary about the code immediately following the
comment. In the screenshot above, you can see at line 29 a comment that
explains that we are searching for the EventProducer service; right
afterwards, we use JUnit's (www.junit.org
) function
assertNotNull
to test that the value
producer
is not null. If the value turns out to be
null, the test will immediately halt with an error. The text "Found the
event producer ok?" will be displayed as part of the failure message. We
find that writing this explanatory text as a question-that explains what
the assertNull or other function is testing-to be a nice way understand
the errors produced. If you see your text in an error message, then you
can assume that some part of the question turned out to not have the
answer you expected. You should go back an examine your
assumptions!
We will discuss the particulars of Events, EventListeners in the next couple of sections. However, for now, you should try to run the tests and make sure that they pass! You can do this by making sure EventTest.java is selected in Eclipse's Package Explorer, then use the context menu (usually bound to the right mouse button) to choose from the menu. This creates, if you don't have it already, a JUnit tab behind the Package Explorer tab in Eclipse. Assuming everything went ok, you should see the fabled and much discussed "green bar" in the upper left region of your display:
For most Java programmers that use JUnit this is the sign that "the world is ok" because all the tests are passing!
For fun, try changing the source the
assertNotNull
shown in the screenshot above (line 31)
to assertNull
; you will probably see something more
like this:
The dreaded "red bar" is shown in this shot! This means that
something is broken. You can see from this snap a lot of information about
the failure: You can see that it happened in the test function
testEventIsHandled
(because of the 'x' next to that
test, as opposed to the check mark by
testDocumentCreationHandled
). In the lower section
you have a stacktrace of the place where the failure occurred: you can
click on items in this stack trace to zip to that location in the source
code! You can also see part of the text comment of the test that was being
attempted, "Found the even..." Be sure to put your test back to the way it
was, with assertNotNull
, or you'll be seeing that red
bar forever!
You should probably read the two tests in
EventTest.java
,
testEventIsHandled
and
testDocumentCreationHandled
, follow along with the
commentary, and look at the Nuxeo and JUnit APIs used. The text of the
book does not cover each line of the code in detail!
We will start with a high level description of the purpose of testEventIsHandled. The objective of this test is very simple: to prove that if we create an event, any event, and send it through the Nuxeo infrastructure, our code under test will get called. The code to be tested for correctness can be seen by opening up the src/main/java directory in Eclipse and
With the preliminaries now over, let us proceed with the point of
this lesson! Events in Nuxeo are notifications, or messages, that
something interesting has happened. In this respect, Nuxeo is identical to
many of the other Java-based systems, such as Java's Swing, that use
Events to indicate to some part of a program that, well, an event has
occurred. The code that receives the notification is referred to as an
EventListener. Nuxeo has dozens of different Events that a program may be
interested in: A common type are events that generated when there are
changes to the repository, such a documents being created, destroyed, or
changing versions. Let us look at a simple EventListener
(this is an interface in Nuxeo) that is contained in the skeleton for this
lesson. You should open up the privary Java source code folder
(src/main/java
) and then look in the package
org.nuxeo.upcoming. There is only one file in there at this
point, DocumentCreationListener.java
:
This code, when put in our bundle, is going to create a title on a document automatically whenever a document of type Upcoming is created. It listens for an event indicating that a document has been created to know when to do its job. This listener is careful to not do so when other documents get created or when other events (that are not document creation events) are sent to it. The title generated for Upcoming documents is "Event XXX" where XXX is the date of the Event, displayed in a locale-specific way. It would be good for the reader to read through the comments in this file that begin with "//" and see the Nuxeo APIs used. The other type of comment, starting with "/**" and terminating with "**/", is used to enclose parts of the code that display interesting information. You can uncomment these and watch the console output to get more insight into how Nuxeo-in this case a Nuxeo EventListener-works. For instance, one of these commented sections in DocumentCreationListener will walk through all the properties that are part of the event and many readers may find this informative.
A key resource for any technical issue with Nuxeo is the Nuxeo Book. Events are covered in Chapter 28; if you reading this book you most likely can ignore all the content in that chapter about "old style" events since this book only covers the "new style." In addition, now that you are using the Java API of Nuxeo 5, you will most likely want to access the JavaDoc to see all the methods and types that you have available in the API.
A key Nuxeo notion seen in DocumentCreationListener is reified in the Java type EventContext and it's descendent type DocumentEventContext. This is contextual information that is associated with an event and the specific type of the context depends on the particular event type. As you can see from the snapshot above (lines 32-34), this Listener ignores any Event that does not have a context that meets the interface DocumentEventContext. This should not be surprise, we are only interested in Documents being created!
Also, critical to most event handlers, including the one shown here is to determine the "source" of the event-in other words, what object logically "caused" the event. Here is the snippet from the DocumentCreationListener.
//this gets the document that is the "cause" of the event, critical! DocumentModel model = context.getSourceDocument();
This returns a reference to the DocumentModel that has just been created. Most of the time, event listeners will need to ask for information from or operate on the source of the event that they are interested in.
Finally, readers familiar with the more strongly-typed event system of Java Swing or other, similar systems should notice that Nuxeo events do not have "subtypes" as the Swing events do (MouseEvent, KeyboardEvent). The way to differentiate between the various types of Nuxeo events is by examining the name field (event.getName) and, if necessary, the type of the EventContext object. The example code given in this lesson does both of these.
Since this is the first lesson with Java code, most readers are likely to be more interested in the APIs used by the test code since these are more varied than the actual code under test. We will discuss the test code in some detail, since it introduces many new concepts.
Here is a listing of the body of the first test function,
testEventIsHandled
:
public void testEventIsHandled() throws Exception { //check for this name, if you want, using the debugger on the event listener String eventName = "my event: there are many like it but this one is mine"; //get the event production service, so we can send an event EventProducer producer=Framework.getService(EventProducer.class); assertNotNull("Found the event producer ok?",producer); //create a fake event object with some bogus values Map<String, Serializable> props = new HashMap<String, Serializable>(); props.put("test-name", "testEventBasic"); //the event context is a really an "EventGenerator" in some sense EventContext context=new InlineEventContext(null,props); Event syntheticEvent = context.newEvent(eventName); //the event service is the center of all event processing... EventService service=Framework.getService(EventService.class); assertNotNull("Found event service ok?",service); //EventListenerHelper is the wrapper around our DocumentCreationListener service.addEventListener(new EventListenerHelper()); //send the event and make sure it got handled! assertFalse("No events handled yet?",props.containsKey(DocumentCreationListener.HANDLED_EVENT)); producer.fireEvent(syntheticEvent); assertTrue("handled our event?",props.containsKey(DocumentCreationListener.HANDLED_EVENT)); }
This test does one, seemingly simple, thing: It creates an event
from scratch (syntheticEvent
) and transmits it to the
code under test that expects events (see previous section). To be sure
that the code under test actually ran, we create a map of properties
(props
) that the EventListener modifies
in an particular way and we test that this modification is observed.
This has no functional value, it just allows us to test "did the code
under test run at all."
In two places, the Nuxeo framework is asked to look up and return
a "service." This done through Framework.getService
and the two services retrieved are the EventProducer and
the EventService. Almost all of Nuxeo's functionality is
organized into services that offer various functions to their clients.
It is required that one access these services via the
Framework.getService
mechanism shown in this
example rather than declaring an instance of these types directly. The
reasons for this are many and varied, but the simplest one is that this
allows the services to not be created until they are first needed, if
they are created at all.
Each service is used once (we are doing a simple test!). The EventProducer is used to transmit the event and this is the "preferred" way for Events to be generated by code that wishes to send events; Nuxeo itself uses this same mechanism when it generates events internally. The EventService is used to "hook up" our event listener to the Event stream. We created a small helper class, EventListenerHelper, that is a thin wrapper around the true class DocumentCreationListener. This wrapper is normally generated by Nuxeo as it reads the XML file that contributes an EventListener to the proper extension point. To avoid the need for deploying our bundle-and to create the simplest possible test from a logical point of view-we created the small wrapper ourselves and added it to the EventService manually.
The code for the other test, that does use the normal Nuxeo bundling mechanisms:
public void testDocumentCreationHandled() throws Exception { String path="mydoc"; //send our upcoming bundle to the infrastructure deployBundle("org.nuxeo.book.upcoming"); //initialize the repository and session. these are implemented in base class openRepository(); CoreSession session=getCoreSession(); //get a document type that represents our code, just to be sure we are ok DocumentType upcoming = session.getDocumentType("Upcoming"); assertNotNull("Does our type exist?",upcoming); //create the object that represents the new document DocumentModel modelDesired=new DocumentModelImpl("/",path,"Upcoming"); //setup the properties that make it an upcoming event: //one man show, today, by Bob Newhart (comedian) at a cost of 25.00 modelDesired.setProperty("upcoming", "occursOn", new Date()); modelDesired.setProperty("upcoming", "presenter", "bob newhart"); modelDesired.setProperty("upcoming", "admissionPrice", new Float(25.00f)); //create the document in the repository DocumentModel modelResult = session.createDocument(modelDesired); //make sure that the path is the parent path (/) plus our new path assertEquals("path is same?","/"+path,modelResult.getPathAsString()); assertEquals("path is same? (sanity)", "/"+path, modelDesired.getPathAsString()); //document is created ok, let's see if event handler ran assertNotSame("the result object is different than the desired object?", modelDesired,modelResult); String titleFromDublinCore = (String)modelDesired.getProperty("dublincore","title"); assertNull("document handler did not run on desired?", titleFromDublinCore); //make sure our handler computed everything correctly titleFromDublinCore = (String)modelResult.getProperty("dublincore","title"); assertTrue("document handler ran ok on result?", titleFromDublinCore.startsWith("Event")); //don't bother saving any of these documents... normally you want to do //coreSession.save() but since this is a test, let's not bother coreSession.cancel(); }
Most test code that would be written for a new Nuxeo bundle will have this type of structure: deploy the bundle or bundles needed for the test, open the repository and get the core session, and then create the document or documents needed for the test. The CoreSession object is perhaps the most central object to the Nuxeo API; it is going to be used in one way or another by almost any Nuxeo application or bundle since it holds the means of accessing the repository, and thus documents.
The process of programmatically creating a document in Nuxeo can
also be seen in this test. The process is to create a new
DocumentModelImpl to implement the
DocumentModel that is desired, shown as
modelDesired
. The DocumentModel
implementation needs to know who the parent is (like a directory in a
normal file system) and the document's type, "Upcoming" in our example
here. After the desired basics are set up, one uses the
CoreSession method createDocument
to
create a "real" DocumentModel, called
modelResult
. In the case we have here, we set the
properties of the upcoming schema on the desired document
model so that the result DocumentModel would also have
these properties. However, the creation of the
modelResult
is an action that Nuxeo generates an
Event for and our EventListener should be called and should
generate a title for the document. This is verified at the end of the
test function by examining the
We have been a bit sloppy in this chapter and blurred the distinction between logical "documents" and the Nuxeo types used to manipulate Documents through the API. This has, unfortunately, been necessary because Nuxeo actually has several different Java types that can be used to manipulate "documents" depending on the situation. The most common for test code is DocumentModel which is lightweight representation of a document, including all the document's properties. A DocumentModel's properties and content are loaded only when accessed (it is lazy) and this optimization is extremely valuable for many applications. A true Nuxeo Document can be very expensive to manipulate, depending on exactly what state the document is in and what features of the system interact with it. For now, it is not terribly wrong for the reader to consider DocumentModel to be the basic "document type" in Nuxeo.
Because we wanted to focus this lesson on developing Java code and testing it in Eclipse, we did not mention earlier that there are other important files in the skeleton provided. We provided them in already working form, unlike previous lessons, because we wanted your tests to work "out of the box."
If you notice, we have added a new extension point contribution
src/main/resources/OSGI-INF/event-contrib.xml
and
added to the MANIFEST.MF
to reflect this new
contribution. The contribution looks like this:
<?xml version="1.0"?> <component name="org.nuxeo.upcoming.event.documentCreationListener"> <extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener"> <listener name="documentCreationListener" async="false" postCommit="false" class="org.nuxeo.upcoming.DocumentCreationListener" priority="140"> </listener> </extension> </component>
This is the pattern for a simple event listener; the critical part
in the listener
tag is to get the correct class name that
implements your listener. The attributes async
,
postCommit
, and priority
are ways that you can
modify under what circumstances your event listener gets called. These
are, for now, unlikely to be of much interest other than possibly
priority
. EventListeners are called in an order based on
their priority, with the lower values having precedence. We do not
recommend creating EventListeners that have an ordering dependency, but
sometimes it is unavoidable and if you have two listener contributions,
you can control which is called first with this attribute. Choosing a
priority that is high, such as the 140 above, is a good practice when
you don't really care about the order of the calls.
For the first time in these lessons, we have now created a test
resource. This test resource is located in the file
src/test/resources/log4j.properties
. This file,
like the contents of src/main/resources
, will be
copied into the bin
subdirectory by Eclipse and the
target
subdirectory by Maven. As a result of the
copying, log4j.properties is in the classpath of the running tests. This
file controls the amount of log data output when running the
tests. It has no effect on normal operation and is not
packaged into the resulting bundle by Maven, since it has no function
other than during the running of the tests. As you can see from the
listing below, we have set the logging level to ERROR
so we will only get output when a test fails. If you need to debug a
bundle that has failing tests, you probably want to change the log level
to INFO
or DEBUG
.
log4j.rootLogger=ERROR, CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%C{1}] %m%n
Eclipse, as we have seen so far in this lesson, is used when you want to run tests and get "quick feedback" about the passing or failing of tests (the green bar). As you can see from the screenshots in this lesson, it usually takes only a few seconds to get the feedback from your tests. When all the tests are passing (and not before!) you should use maven to build, test (again), and package up your bundle. Let's try that for the skeleton in this lesson:
/nuxeo/workspace/lesson-events$ mvn clean install [INFO] Scanning for projects... -- lines elided for clarity -- [INFO] [compiler:testCompile] [INFO] Compiling 1 source file to /nuxeo/workspace/lesson-events/target/test-classes [INFO] [surefire:test] [INFO] Surefire report directory: /nuxeo/workspace/lesson-events/target/surefire-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.nuxeo.upcoming.test.EventTest Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.751 sec Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 -- lines elided for clarity -- [INFO] Building jar: /nuxeo/workspace/lesson-events/target/lesson-events-0.0.1-sources.jar [INFO] [install:install] [INFO] Installing /nuxeo/workspace/lesson-events/target/lesson-events-0.0.1.jar to /home/ismith/.m2/repository/org/nuxeo/community/book/lesson-events/0.0.1/lesson-events-0.0.1.jar [INFO] Installing /nuxeo/workspace/lesson-events/target/lesson-events-0.0.1-sources.jar to /home/ismith/.m2/repository/org/nuxeo/community/book/lesson-events/0.0.1/lesson-events-0.0.1-sources.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 13 seconds [INFO] Finished at: Sat Feb 28 12:12:12 CET 2009 [INFO] Final Memory: 55M/408M [INFO] ------------------------------------------------------------------------
We have shown two key things in this example. Now that we have some
test code in src/test/java
, it is found by Maven
during the build process and the tests are executed. It is good practice
to check that maven runs all the tests successfully; there are cases where
the classpath of Eclipse can include extra libraries that are not in the
maven classpath and thus the maven tests will fail. This is a critical
indicator, because the maven classpath is the same as the "real" one that
will be used as your bundle is loaded into Nuxeo server. You can see a
summary of the tests in the line above that starts with "Tests run:"... if
there are failures, you can find the information about the failures in
files in the directory target/surefire-tests
.
The other thing to note is that the
lesson-events-0.0.1.jar
gets built and placed in
the directory target
as well as some other places.
However, if you inspect that file carefully (using jar
or your filesystem browser) it does not contain the
test code or test libraries (such as JUnit). Maven is smart enough to not
package the parts you need only for running the tests! You can see that
there is only the EventListener class in our bundle like
this:
/nuxeo/workspace/lesson-events/target$ jar tf lesson-events-0.0.1.jar | grep .class
org/nuxeo/upcoming/DocumentCreationListener.class
As before, you should copy your bundle from the
target
directory of your workspace to the
plugins
directory of your Nuxeo 5 server. Then create
a new document of type Upcoming, as in shown in this screen snap:
After we hit the Create button, we get a list of the document's properties and "No Chance" indeed did not have a chance!
So, we have now seen the output of the EventListener in action!