You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2010/12/17 17:35:31 UTC
svn commit: r1050444 [2/2] -
/incubator/isis/trunk/core/src/docbkx/guide/isis-core.xml
Modified: incubator/isis/trunk/core/src/docbkx/guide/isis-core.xml
URL: http://svn.apache.org/viewvc/incubator/isis/trunk/core/src/docbkx/guide/isis-core.xml?rev=1050444&r1=1050443&r2=1050444&view=diff
==============================================================================
--- incubator/isis/trunk/core/src/docbkx/guide/isis-core.xml (original)
+++ incubator/isis/trunk/core/src/docbkx/guide/isis-core.xml Fri Dec 17 16:35:31 2010
@@ -67,16 +67,36 @@
License v2</ulink>.</para>
<para>This guide is written for programmers looking to understand how
- <emphasis>Apache Isis</emphasis> fits together, what APIs it defines, what
- the default implementations are. It also explains the principles of
- deploying an Isis application and describes how to do so as a standalone
- or as a webapp.</para>
+ <emphasis>Apache Isis</emphasis> fits together, what
+ <acronym>API</acronym>s it defines, what the default implementations are.
+ It also explains the principles of deploying an Isis application and
+ describes how to do so as a standalone application or as a webapp.</para>
+
+ <para>It is divided into four parts:<itemizedlist>
+ <listitem>
+ <para>Introduction</para>
+
+ <para>The principles and patterns of the framework, understanding
+ how an Isis application works, getting started with the quickapp
+ archetype, the process of developing Isis applications, and tips for
+ developing with an <acronym>IDE</acronym></para>
+ </listitem>
+
+ <listitem>
+ <para>Architecture</para>
+
+ <para>The core framework, the default implementation of APIs, and
+ alternative implementations. </para>
+ </listitem>
+ </itemizedlist></para>
+
+ <para></para>
</preface>
<!-- main content -->
- <part>
- <title>Introduction and Architecture</title>
+ <part id="prt.Introduction">
+ <title>Introduction</title>
<partintro>
<para>*** part intro.</para>
@@ -90,24 +110,64 @@
<title>Overview</title>
<abstract>
- <para>This chapter gives an overview of the main principles and
- patterns that underly the Apache Isis approach.</para>
+ <para>***</para>
</abstract>
+ <para><emphasis>Apache Isis</emphasis> is a full-stack open source
+ application development framework, designed to let you rapidly develop
+ enterprise business applications following a domain-driven philosophy.
+ Developing an application in Isis is - at least initially - about
+ focusing on the bit that matters to the business, the core domain
+ logic.</para>
+
<sect1>
- <title>Introduction</title>
+ <title>What's in this Guide</title>
- <para><emphasis>Apache Isis</emphasis> is a full-stack open source
- application development framework, designed to let you rapidly develop
- enterprise business applications following a domain-driven philosophy.
- Developing an application in Isis is - at least initially - about
- focusing on the bit that matters to the business, the core domain
- logic.</para>
+ <para></para>
<para></para>
+ </sect1>
+
+ <sect1>
+ <title>This Guide vs the Core Documentation</title>
+
+ <para>This guide assumes that you have at least the outline of a
+ running <emphasis>Apache Isis</emphasis> application, for example
+ having run the quickstart archetype. If you haven't done this, then
+ please consult the core documentation to learn how.</para>
+
+ <para>You should also note that the core documentation also explains
+ the wider landscape of what makes up an <emphasis>Isis</emphasis>
+ application, dealing with such matters as running the app with
+ different components, eg viewers or object stores.</para>
+
+ <para>Indeed, it is also possible for developers to build their own
+ programming model, either as a subset of the default programming model
+ described here, and possibly with additional custom elements. See the
+ core documentation for details on how to write your own programming
+ model.</para>
+
+ <para>However, everything related to the specifics of writing a domain
+ object with the <emphasis>default</emphasis> programming model is
+ here.</para>
+ </sect1>
+
+ <sect1>
+ <title>Where Next?</title>
<para></para>
</sect1>
+ </chapter>
+
+ <chapter>
+ <title>Principles & Patterns</title>
+
+ <abstract>
+ <para>An overview of the main principles and patterns that underly the
+ <emphasis>Apache Isis</emphasis> approach.</para>
+ </abstract>
+
+ <para></para>
<sect1>
<title>Principles</title>
@@ -374,2761 +434,2801 @@
</chapter>
<chapter>
- <title>Running the QuickApp Archetype</title>
+ <title>Understanding how an Isis application works</title>
- <para>*** talk through using the "quickapp" archetype, which generates a
- read-to-run app.</para>
+ <abstract>
+ <para>***</para>
+ </abstract>
<para></para>
+ <para>*** using the default programming model conventions.</para>
+
<para></para>
- <sect1>
- <title>Run Archetype</title>
+ <para>[[NAME]] creates the user interface for an application directly
+ from the definitions of the domain model. In this section we will look
+ at that relationship in more detail, with reference to the Expenses
+ Processing example application supplied as part of the download. As we
+ showed in the previous section, any domain model written for [[NAME]]
+ may be run with any of the viewers - there is no specific coding
+ required, and the domain model has no knowledge of which viewer is being
+ used. However, each viewer will have different gestures or mechanisms
+ for providing the same functionality. To illustrate this, we will show
+ the same objects being accessed through both the DND and the HTML
+ viewers, side by side.</para>
- <para></para>
+ <para>The application code for the Expenses Processing example, like any
+ [[NAME]] application, consists of two things: domain objects and
+ services. The domain objects form the lion's share of that code, so
+ we'll look at how those work first.</para>
- <para></para>
+ <para>The code for examples we will be looking at can be found in the
+ directory <filename>examples/expenses/expenses-dom/src</filename> in the
+ downloaded files.</para>
- <para></para>
+ <sect1>
+ <title>Domain objects</title>
- <para>Using Maven archetype plugin we can generate a new [[NAME]]
- application very quickly. These are multi-moduled projects with
- separate sections for the domain code, fixtures, web interfaces and so
- on.</para>
+ <para>The domain objects are the entities - the nouns - that represent
+ the application domain: employee, claim, expense item, project code,
+ currency, and so forth. In the course of using the application, a user
+ will view and manipulate many instances of these domain objects. To
+ understand how [[NAME]] handles domain objects, we'll start by looking
+ at an Employee object:</para>
- <para>One option is to create the archetype from the commandline. Note
- that if you are using Eclipse and have installed the m2eclipse plugin
- then it provides a dialog to create the project from an archetype;
- this is discussed below.</para>
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%"
+ fileref="images/employee-views.png" format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
- <remark>TODO: update the listing below, it is out of date. The
- archetype to use is called
- 'org.apache.isis:application-archetype'.</remark>
+ <para>Every object presented in the user interface will have a
+ corresponding Java class in the domain model - in this case it is
+ <classname>org.apache.isis.example.expenses.employee.Employee</classname>.
+ Below we can see the code for the <classname>Employee</classname>
+ object, as presented in Eclipse, with the object's list of methods
+ presented on the left hand side.</para>
- <screen>[rcm@localhost ~]$ <emphasis role="bold">mvn archetype:generate -DarchetypeCatalog=http://isis.apache.org</emphasis>
-[INFO] Scanning for projects...
-[INFO] Searching repository for plugin with prefix: 'archetype'.
-[INFO] ------------------------------------------------------------------------
-[INFO] Building Maven Default Project
-[INFO] task-segment: [archetype:generate] (aggregator-style)
-[INFO] ------------------------------------------------------------------------
-[INFO] Preparing archetype:generate
-[INFO] No goals needed for project - skipping
-[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
-[INFO] Setting property: velocimacro.messages.on => 'false'.
-[INFO] Setting property: resource.loader => 'classpath'.
-[INFO] Setting property: resource.manager.logwhenfound => 'false'.
-[INFO] [archetype:generate]
-[INFO] Generating project in Interactive mode
-[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
-Choose archetype:
-1: local -> isis-application ([[NAME]] Application (Archetype))
-2: local -> isis-icons ([[NAME]] Icons (Archetype))
-3: local -> htmlviewer-war ([[NAME]] WAR (Archetype))
-4: local -> hibernate-support (Hibernate Support (Archetype))
-5: local -> remoting-support ([[NAME]] Remoting Support (Archetype))
-Choose a number: (1/2/3/4/5): <emphasis role="bold">1</emphasis></screen>
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%" fileref="images/employee-code.png"
+ format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
- <para>To create a project from an archetype you must specify a
- <emphasis>groupId</emphasis> and <emphasis>artifactId</emphasis>, a
- <emphasis>version</emphasis> and a <emphasis>Java
- package</emphasis>.</para>
+ <para>The first thing to note is that the type of the object as shown
+ in the user views is derived directly from the class name in Java. The
+ framework inserts spaces before capital letters, so that the class
+ <classname>TemporaryEmployee</classname> would be presented to the
+ user as 'Temporary Employee'. However we will see later that the name
+ may be over-ridden where necessary, for example if we want the name to
+ include punctuation or other characters not allowed in Java class
+ names. (Note that there is a separate mechanism for dealing with
+ internationalisation).</para>
- <para>After choosing the archetype number and pressing enter you are
- prompted for the project's details. The <emphasis>groupId</emphasis>
- is an identifier representing your company/group; ours would typically
- be "org.apache.isis" for the domain followed by a name for the group
- of products. The <emphasis>artifactId</emphasis> identifies the
- projects that we are creating. The <emphasis>version</emphasis>
- indicates how mature the project is, and should be left as the
- default, e.g. 1.0-SNAPSHOT. Finally, the <emphasis>package</emphasis>
- is the base package name used for all Java files. After these have
- been entered you are prompted to confirm by entering Y, as shown
- below.</para>
+ <para>Secondly, we can see that <classname>Employee</classname>
+ extends <classname>AbstractDomainObject</classname> - a class provided
+ within the [[NAME]] application library. This is not a requirement:
+ your domain objects may be Plain Old Java Objects (POJOs) - they do
+ not need to extend any class in the framework. However, extending from
+ <literal moreinfo="none">AbstractDomainObject</literal> will save us
+ having to write a few lines of code in each case, as we'll see
+ later.</para>
- <para>Be wary of using invalid names. The <emphasis>groupId</emphasis>
- should be alphanumeric with dots to separate the words. The
- <emphasis>artifactId</emphasis> should also be alphanumeric, with '-'
- (hyphens) to separate the words. The <emphasis>package</emphasis> name
- should be a valid Java package name, i.e. should have no spaces or
- dashes. Maven does little to check these things.</para>
+ <para>Note also that in the body of the object we use 'code folding'
+ (the plug-in used here is <ulink
+ url="http://www.realjenius.com/platform_support">Coffee Bytes</ulink>)
+ to break the object's code into regions, each typically containing one
+ or more related methods that together fulfill a high-level
+ responsibility of the object. This is just a coding convention, not a
+ requirement.</para>
+ </sect1>
- <screen>Define value for groupId: : <emphasis role="bold">org.example</emphasis>
-Define value for artifactId: : <emphasis role="bold">expenses</emphasis>
-Define value for version: 1.0-SNAPSHOT: :
-Define value for package: : <emphasis role="bold">org.example.expenses</emphasis>
-Confirm properties configuration:
-groupId: org.example
-artifactId: expenses
-version: 1.0-SNAPSHOT
-package: org.example.expenses
- Y: : <emphasis role="bold">y</emphasis>
-[INFO] ------------------------------------------------------------------------
-[INFO] BUILD SUCCESSFUL
-[INFO] ------------------------------------------------------------------------
-[INFO] Total time: 1 minute 13 seconds
-[INFO] Finished at: Thu Oct 02 20:42:50 BST 2008
-[INFO] Final Memory: 13M/247M
-[INFO] ------------------------------------------------------------------------
-</screen>
+ <sect1>
+ <title>Properties</title>
- <para>This will create a new directory with the name of the
- <emphasis>artifactId</emphasis>. So in this example this is
- <filename>expenses</filename> as shown below.<screen>expenses
-|-- pom.xml
-|-- dom
-| |-- pom.xml
-| `-- src
-|-- commandline
-| |-- config
-| |-- ide
-| |-- pom.xml
-| `-- src
-|-- fixture
-| |-- pom.xml
-| `-- src
-|-- service
-| |-- pom.xml
-| `-- src
-`-- webapp
- |-- pom.xml
- `-- src</screen></para>
+ <para>In both of the user views of an Employee we can see a field
+ called 'Name'. Within the <classname>Employee</classname> class there
+ is a <literal moreinfo="none">Name</literal> region of code, expanded
+ here:</para>
- <para></para>
- </sect1>
+ <programlisting condition="" format="linespecific">// {{ Name
+ private String name;
- <sect1>
- <title>Building the application</title>
+ @MemberOrder(sequence="1")
+ @Disabled
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+// }}</programlisting>
- <para>Once an archetype is created it can be built using Maven and
- run. Build using the <emphasis>install</emphasis> goal as shown
- here.</para>
+ <para>The <literal moreinfo="none">Name</literal> region contains a
+ simple property, of type <literal moreinfo="none">String</literal>,
+ defined by a <literal moreinfo="none">getName</literal> and a <literal
+ moreinfo="none">setName</literal> method. This is sufficient
+ information to allow the viewers to display a field containing a
+ textual value. Note that if the property is to be persisted, then it
+ will need both a <literal moreinfo="none">get</literal> and a <literal
+ moreinfo="none">set</literal> method - per the standard JavaBeans
+ convention. If you wish to display a field that is derived
+ automatically from other information in the object, and do not require
+ this to be persisted, then a <literal moreinfo="none">get</literal>
+ alone will suffice.</para>
- <screen>[rcm@localhost expenses]$ <emphasis role="bold">mvn clean install</emphasis>
-[INFO] Scanning for projects...
-[INFO] Reactor build order:
-[INFO] [[NAME]] Application
-[INFO] Domain Model
-[INFO] Services
-[INFO] Fixtures
-[INFO] Exploration
-[INFO] ------------------------------------------------------------------------
-[INFO] Building [[NAME]] Application
-[INFO] task-segment: [clean, install]
-[INFO] ------------------------------------------------------------------------
-[INFO] [clean:clean]
-[INFO] [site:attach-descriptor]
-[INFO] [install:install]
-[INFO] Installing /home/rcm/tmp/bearingpoint/expenses/pom.xml to /home/rcm/.m2/repository/org/example/expenses/1.0-SNAPSHOT/expenses-1.0-SNAPSHOT.pom
-[INFO] ------------------------------------------------------------------------
-[INFO] Building Domain Model
-[INFO] task-segment: [clean, install]
-[INFO] ------------------------------------------------------------------------
-[INFO] [clean:clean]
-[INFO] Deleting directory /home/rcm/tmp/bearingpoint/expenses/dom/target
-[INFO] [resources:resources]
-[INFO] Using default encoding to copy filtered resources.
-[INFO] [compiler:compile]
-[INFO] Compiling 7 source files to /home/rcm/tmp/bearingpoint/expenses/dom/target/classes
-[INFO] [resources:testResources]
-[INFO] Using default encoding to copy filtered resources.
-:
-:
-[INFO] [resources:testResources]
-[INFO] Using default encoding to copy filtered resources.
-[INFO] [compiler:testCompile]
-[INFO] No sources to compile
-[INFO] [surefire:test]
-[INFO] No tests to run.
-[INFO] [jar:jar]
-[INFO] Building jar: /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses.jar
-[INFO] [assembly:attached {execution: default}]
-[INFO] Reading assembly descriptor: src/main/assembly/descriptor.xml
-[INFO] Processing DependencySet (output=lib)
-[INFO] Copying files to /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses-exploration-1.0-SNAPSHOT-prototype.dir
-[WARNING] Assembly file: /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses-exploration-1.0-SNAPSHOT-prototype.dir is not a regular file (it may be a directory). It cannot be attached to the project build for installation or deployment.
-[INFO] [install:install]
-[INFO] Installing /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses.jar to /home/rcm/.m2/repository/org/example/expenses-exploration/1.0-SNAPSHOT/expenses-exploration-1.0-SNAPSHOT.jar
-[INFO]
-[INFO]
-[INFO] ------------------------------------------------------------------------
-[INFO] Reactor Summary:
-[INFO] ------------------------------------------------------------------------
-[INFO] [[NAME]] Application .............................. SUCCESS [6.087s]
-[INFO] Domain Model .......................................... SUCCESS [8.711s]
-[INFO] Services .............................................. SUCCESS [2.432s]
-[INFO] Fixtures .............................................. SUCCESS [2.283s]
-[INFO] Commandline ........................................... SUCCESS [10.774s]
-[INFO] ------------------------------------------------------------------------
-[INFO] ------------------------------------------------------------------------
-[INFO] BUILD SUCCESSFUL
-[INFO] ------------------------------------------------------------------------
-[INFO] Total time: 31 seconds
-[INFO] Finished at: Tue Mar 03 11:59:50 GMT 2009
-[INFO] Final Memory: 40M/495M
-[INFO] ------------------------------------------------------------------------
-[rcm@localhost expenses]$ </screen>
+ <para>As with the name of the object, the field name is derived
+ automatically from the name of the property - though we'll see later
+ that this may be over-ridden if needed.</para>
- <para>Once the project is built the results can be found in the
- <filename>exploration/target</filename> directory, which contains both
- a zipped distribution (in this example
- <filename>expenses-exploration-1.0-SNAPSHOT-prototype.zip</filename>)
- and an expanded distribution (again for this example,
- <filename>expenses-exploration-1.0-SNAPSHOT</filename>). Using the
- contained script files the application can be run from the command
- line.<screen>exploration
-|-- config
-|-- ide
-|-- pom.xml
-|-- src
-`-- target
- |-- archive-tmp
- |-- classes
- |-- expenses-exploration-1.0-SNAPSHOT-prototype.dir
- | `-- expenses-exploration-1.0-SNAPSHOT
- | |-- config
- | |-- images
- | |-- expenses.jar
- | |-- isis.bat
- | |-- isis.sh
- | |-- lib
- |-- expenses-exploration-1.0-SNAPSHOT-prototype.zip
- |-- expenses.jar
- `-- maven-archiver</screen></para>
- </sect1>
+ <para>The <literal moreinfo="none">getName</literal> has been marked
+ up with two Java annotations, both defined in the [[NAME]] application
+ library. Annotations allow the programmer to enrich the information
+ available to the framework. On properties, any [[NAME]] Objects
+ annotations are always associated with the <literal
+ moreinfo="none">get</literal> method. However, annotations are not
+ mandatory - you can write a simple [[NAME]] application without using
+ any annotations at all.</para>
- <sect1>
- <title>Running with the DnD Viewer</title>
+ <para>By default, any property with both a <literal
+ moreinfo="none">get</literal> and <literal
+ moreinfo="none">set</literal> method will be editable by the user.
+ <literal moreinfo="none">@Disabled</literal> tells the framework that
+ this particular property may never be altered by the user (though it
+ may be altered programmatically). Later we'll see how to make a
+ property modifiable on certain conditions.</para>
- <para>*** below was taken from the tutorial on running the old
- expenses demo</para>
+ <para><literal moreinfo="none">@MemberOrder(sequence="1")
+ </literal>tells the framework that this property should be the first
+ field displayed in any view of the Employee - irrespective of where it
+ is defined within the code. This ordering information has been
+ observed by both the viewers.</para>
- <para></para>
+ <para>The next region of the code contains another <literal
+ moreinfo="none">String</literal> property, called <literal
+ moreinfo="none">UserName</literal>:</para>
- <para>The executable version of the Expenses demo application exists
- within the <filename class="directory"
- moreinfo="none">demos</filename> directory of the [[NAME]]
- distribution.</para>
+ <programlisting format="linespecific">// {{ UserName field
+ private String userName;
- <para>Run the batch file <filename class="directory"
- moreinfo="none">ExpensesDND.bat</filename> file either by
- double-clicking on the icon (in Windows) or using the following
- commands on the command line. In Windows the commands are</para>
+ @Hidden
+ public String getUserName() {
+ return userName;
+ }
+ public void setUserName(final String variable) {
+ this.userName = variable;
+ }
+// }}</programlisting>
- <programlisting>> cd demos\expenses
-> ExpensesDND.bat</programlisting>
+ <para>Note that <literal moreinfo="none">getUserName</literal> has
+ been marked up with <literal moreinfo="none">@Hidden</literal>. This
+ tells the framework that this property should never be shown in user
+ views of the object (check this against the two user views above).
+ Later on we'll see how it is possible to hide a property in certain
+ circumstances.</para>
- <para>while on the Mac or when using Unix/Linux they are:</para>
+ <para>Next we'll look at the <literal
+ moreinfo="none">EmailAddress</literal> region:</para>
- <programlisting>> cd demos/expenses
-> ./ExpensesDND.sh</programlisting>
+ <programlisting format="linespecific">// {{ EmailAddress
+ private String emailAddress;
+
+ @MemberOrder(sequence = "2")
+ @Optional
+ @RegEx(validation = "(\\w+\\.)*\\w+@(\\w+\\.)+[A-Za-z]+")
+ public String getEmailAddress() {
+ return this.emailAddress;
+ }
+ public void setEmailAddress(final String emailAddress) {
+ this.emailAddress = emailAddress;
+ }
+ public void modifyEmailAddress(final String emailAddress) {
+ getRecordActionService().recordFieldChange(this, "Email Address", getEmailAddress(), emailAddress);
+ setEmailAddress(emailAddress);
+ }
+ public void clearEmailAddress() {
+ getRecordActionService().recordFieldChange(this, "Email Address", getEmailAddress(), "EMPTY");
+ setEmailAddress(null);
+ }
+ public boolean hideEmailAddress() {
+ return !employeeIsCurrentUser();
+ }
+ private boolean employeeIsCurrentUser() {
+ return getUserFinder().currentUserAsObject() == this;
+ }
+// }}</programlisting>
- <para>After seeing the [[NAME]] splash screen, you'll be presented
- with a Login screen:</para>
+ <para>As well as <literal moreinfo="none">@MemberOrder</literal>, this
+ property is marked up with <literal
+ moreinfo="none">@Optional</literal> and <literal
+ moreinfo="none">@RegEx</literal> annotations. By default, all
+ properties are taken to be mandatory - if the user creates or edits an
+ object then they will be required to specify the contents of each
+ field. <literal moreinfo="none">@Optional</literal> overrides this
+ default behaviour - indicating here that the object may be saved
+ without an email address.</para>
- <screenshot>
- <mediaobject>
- <imageobject>
- <imagedata contentwidth="40%" fileref="images/log-on-dnd.png"
- format="PNG" />
- </imageobject>
- </mediaobject>
- </screenshot>
+ <para><literal moreinfo="none">@RegEx</literal> is applicable only to
+ <literal moreinfo="none">String</literal> properties. In this case the
+ annotation specifies a Regular Expression that will be used to
+ validate any value that the user types into the field. In a
+ conventional architecture, this functionality would typically be found
+ in the user interface code. The [[NAME]] argument is that this
+ functionality should apply to any user interface that might want to
+ change the property, so its proper place is in the object. <literal
+ moreinfo="none">@RegEx</literal> may also be used to reformat a String
+ that has been entered by the user.</para>
- <para>Log in as 'sven' and a password of 'pass'. The application will
- then open like this:</para>
+ <para>The two screens below show how two different viewers make use of
+ the functionality in different ways. In both cases the user has typed
+ in a value that does not match the RegEx specification (they have
+ typed in an email address that contains a space), so the new value has
+ not been accepted or saved.</para>
<screenshot>
<mediaobject>
<imageobject>
- <imagedata contentwidth="40%" fileref="images/home-dnd.png"
+ <imagedata contentwidth="40%"
+ fileref="images/employee-email-invalid.png"
format="PNG" />
</imageobject>
</mediaobject>
</screenshot>
- <para>When an application is started an application window is opened
- and the user's services are displayed on the left hand side. These
- icons typically give the user access to the domain objects held and
- used by the system, and provide a way for to create new instances. Any
- other object that subsequently appears on the screen represents one of
- those objects.</para>
-
- <remark>TODO replace with up-to-date shot using current
- example</remark>
-
- <screenshot>
- <screeninfo></screeninfo>
-
- <mediaobject>
- <imageobject>
- <imagedata contentwidth="40%" fileref="images/dnd-example.png" />
- </imageobject>
- </mediaobject>
- </screenshot>
+ <para>In addition to <literal
+ moreinfo="none">getEmailAddress</literal> and <literal
+ moreinfo="none">setEmailAddress</literal>, there are <literal
+ moreinfo="none">modifyEmailAddress</literal>, <literal
+ moreinfo="none">clearEmailAddress</literal> and <literal
+ moreinfo="none">hideEmailAddress</literal> methods. [[NAME]]
+ recognises the <literal moreinfo="none">modify</literal>, <literal
+ moreinfo="none">clear</literal> and <literal
+ moreinfo="none">hide</literal> prefixes (and a few others that we
+ shall see later) as specifying additional functionality relating to
+ the <literal moreinfo="none">EmailAddress</literal> property.</para>
- <para>The service and objects can be manipulated as follows:</para>
+ <para>If a property has a corresponding <literal
+ moreinfo="none">modify<propertyName></literal> method, then
+ whenever the user modifies the field, this will be called rather than
+ the <literal moreinfo="none">set</literal>. In this case the <literal
+ moreinfo="none">modify</literal> method uses the <literal
+ moreinfo="none">RecordActionService</literal> to record the details of
+ the change, and then calls <literal
+ moreinfo="none">setEmailAddress</literal> to change the value. The
+ reason for adopting this pattern, rather than including the
+ functionality in the <literal moreinfo="none">set</literal> itself, is
+ that the <literal moreinfo="none">set</literal> will be called by the
+ object store each time the object is retrieved. So we use a <literal
+ moreinfo="none">modify</literal> method where we want to do something
+ (such as add to a total) only when the user changes a field.</para>
- <itemizedlist>
- <listitem>
- <para>Object actions can be invoked by
- <emphasis>right-clicking</emphasis> on an icon and selecting one
- of the options from the menu.</para>
- </listitem>
-
- <listitem>
- <para>Resource actions can be invoked by
- <emphasis>right-clicking</emphasis> on a resource icon.To inspect
- an object (when only an icon is shown)
- <emphasis>double-click</emphasis> it - this normally opens up the
- object in a new window.</para>
- </listitem>
-
- <listitem>
- <para>Objects can be dragged by
- <emphasis>click-dragging</emphasis> on the icons and dropped by
- releasing the mouse button.</para>
- </listitem>
-
- <listitem>
- <para>Dragged objects can be dropped on an empty object field
- (identified by the grey hole; the label to the right of the hole
- specifies what type of object can be dropped). If the object can
- be dropped into that field then it will flash green. If the object
- cannot be dropped - either because it is the wrong type or that
- specific object is disallowed for some other reason - then the
- field will flash red. Dropping an object into a field sets up that
- field, effectively associating the two objects.</para>
- </listitem>
+ <para><literal moreinfo="none">clearEmailAddress</literal> is called,
+ in a similar manner, if the user clears the contents of the field.
+ Again, it is optional - added where we want to perform some logic only
+ when the user clears the property. On the <literal
+ moreinfo="none">UserName</literal> field we saw that <literal
+ moreinfo="none">@Hidden</literal> hides a property from the user
+ permanently. We may, however, want to hide fields under certain
+ circumstances. The visibility of all classes, properties and methods
+ may be controlled via conventional authorization techniques, based on
+ the user's role(s). In rarer cases, we want to control visibility at
+ an instance level. In this case, for privacy reasons we do not want
+ the email address to be visible, except to that person. This is what
+ the <literal moreinfo="none">hideEmailAddress()</literal>method is
+ doing. If the method returns true, the field will be hidden from the
+ user.</para>
- <listitem>
- <para>Dragged objects can be dropped on to other object icons. If
- the object that you attempting to drop onto accepts that type of
- object then if will flash green. If that object does not accept
- that kind of object, or disallows that specific object, then it
- will flash red. Dropping one object on to another invokes a
- specific object action, for example: add this product to that
- order.</para>
- </listitem>
+ <para>Next we will look at the <literal
+ moreinfo="none">NormalApprover</literal> region:</para>
- <listitem>
- <para>To quit the application, or access other application related
- options right-click on the desktop to bring up the system
- menu.</para>
- </listitem>
- </itemizedlist>
+ <programlisting format="linespecific">// {{ NormalApprover
+ private Employee normalApprover;
+
+ @MemberOrder(sequence="4")
+ public Employee getNormalApprover() {
+ return this.normalApprover;
+ }
+ public void setNormalApprover(final Employee normalAuthoriser) {
+ this.normalApprover = normalAuthoriser;
+ }
+ public void modifyNormalApprover(final Employee normalAuthoriser) {
+ getRecordActionService().recordFieldChange(this, "Normal Approver", getNormalApprover(), normalApprover);
+ setNormalApprover(normalAuthoriser);
+ }
+ public void clearNormalApprover() {
+ getRecordActionService().recordFieldChange(this, "Normal Approver", getNormalApprover(), "EMPTY");
+ setNormalApprover(null);
+ }
+ public String validateNormalApprover(Employee newApprover) {
+ return newApprover == this ? CANT_BE_APPROVER_FOR_OWN_CLAIMS: null;
+ }
+ public String disableNormalApprover() {
+ return employeeIsCurrentUser() ? null: NOT_MODIFIABLE;
+ }
+
+ public static final String NOT_MODIFIABLE = "Not modifiable by current user";
+ public static final String CANT_BE_APPROVER_FOR_OWN_CLAIMS = "Can't be the approver for your own claims";
+// }}</programlisting>
- <para></para>
- </sect1>
+ <para>The <literal moreinfo="none">NormalApprover</literal> property
+ takes an object of type <literal moreinfo="none">Employee</literal>.
+ Assuming that this field is not disabled, the user may specify an
+ Employee object for this field. [[NAME]] will prevent the user from
+ trying to associate the wrong type of object with this field. This is
+ illustrated in the two screens below:</para>
- <sect1>
- <title>Running with the HTML Viewer</title>
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%"
+ fileref="images/employee-approver-views.png"
+ format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
- <para>*** below was taken from the tutorial on running the old
- expenses demo</para>
+ <para>In the left-hand screen (DND) we can see the user dropping an
+ Employee object into the empty field, and the field is flashing green
+ to indicate that this will succeed. If the user attempted to drop
+ another type of object into the empty field, then the field would
+ flash red, and the drop would not update the field. A successful drop
+ will call the <literal moreinfo="none">set</literal> method, or, if a
+ <literal moreinfo="none">modify<propertyName></literal> method
+ is provided (as it is here), it will call that instead. Note that on
+ the DND viewer, if a field already contains an object, then this may
+ be cleared by right-clicking on that object and selecting 'Clear
+ Association'. This will set the property to <literal
+ moreinfo="none">null</literal>. If there is a <literal
+ moreinfo="none">clear<propertyName></literal> field (as there is
+ in this example) then that will be called rather than the <literal
+ moreinfo="none">set</literal> method. Alternatively a new reference
+ can be dropped on to the field's label, which combines both the
+ clearing and the subsequent setting of the field.</para>
- <para></para>
+ <para>In the HTML viewer (right-hand screen) drag and drop is not
+ possible. In a reference field such as this one, the user will be
+ given a drop-down list of objects of the appropriate type (i.e.
+ Employees here) that the user has recently viewed. If the required
+ Employee object is not in that list then the user may go and find that
+ object (e.g. from the Employees tab) and then return to the context -
+ this time the newly viewed Employee will have been added to the list
+ automatically. (Note: This is a generic capability provided by the
+ HTML viewer. In other contexts, the programmer may want to specify an
+ explicit list of objects to appear in a drop-down list. This would be
+ achieved by means of a <literal
+ moreinfo="none">choices<propertyName></literal> method).</para>
- <para>2</para>
+ <para>The <literal moreinfo="none">validateNormalApprover</literal>
+ method enforces any rules concerning the specific instances of
+ <literal moreinfo="none">Employee</literal> that may be associated
+ with this field. In this particular example, it prevents the user from
+ specifying an Employee as their own approver. Note that this method
+ returns a <literal moreinfo="none">String</literal>. If the specific
+ Employee instance being passed into the method is acceptable, the
+ method should return <literal moreinfo="none">null</literal>; if
+ unacceptable then the method should return a <literal
+ moreinfo="none">String</literal> message that will be made available
+ to the user to advise them why the action will not succeed. (On the
+ DND this appears at the bottom of the screen.)</para>
- <para>To run the expenses demo with the HTML user interface, run the
- batch file <filename class="directory"
- moreinfo="none">ExpensesHTML.bat</filename> either by double-clicking
- on the icon (in Windows) or using the following commands on the
- command line. In Windows the commands are.</para>
+ <para>The <literal moreinfo="none">disableNormalApprover</literal>
+ method prevents the user from modifying the field in certain
+ circumstances. In this example the method enforces the rule that only
+ the Employee themselves may change this field. Like the <literal
+ moreinfo="none">validate</literal> method, it returns a <literal
+ moreinfo="none">null</literal> if the user may modify the field
+ (subject to the validate rules), or returns a <literal
+ moreinfo="none">String</literal> message if they may not. (Note that
+ this method, along with hide (seen earlier) allow for 'instance-based
+ authorization'. Most applications can manage with 'class-based
+ authorization' - in which the classes, properties and actions made
+ available to a user are based on their roles. Class-based
+ authorization in [[NAME]] is administered externally to the
+ application and does not require any coding within the domain
+ objects.)</para>
+ </sect1>
- <programlisting>> cd demos\expenses
-> ExpensesHTML.bat</programlisting>
+ <sect1>
+ <title>Title</title>
- <para>while on the Mac or when using Unix/Linux they are:</para>
+ <para>In the next screen we will look at the <literal
+ moreinfo="none">title</literal> region of the Employee object.</para>
- <programlisting>> cd demos/expenses
-> ./ExpensesHTML.sh</programlisting>
+ <programlisting format="linespecific">// {{ Title
+ public String title() {
+ return getName();
+ }
+// }}</programlisting>
- <para>Allowing a few seconds for the application to start up, you then
- need to launch a browser and point it to <filename class="directory"
- moreinfo="none">http://localhost:8080/logon.app</filename> and you'll
- be presented with a Login screen.</para>
+ <para>The <literal moreinfo="none">title</literal> method specifies
+ the title for the object - which, on both the DND and HTML viewers
+ appears next to the icon. The title is there to help the user identify
+ objects. [[NAME]] also provides an easy mechanism to retrieve objects
+ from the object store by their title. Other methods of
+ finding/searching may require repository methods to be written. If no
+ <literal moreinfo="none">title</literal> method is specified, [[NAME]]
+ Objects will use the object's <literal
+ moreinfo="none">toString</literal> method as a title. Titles are
+ usually based on one or more of the persisted properties - in this
+ case on the Name. When constructing a title from multiple elements,
+ the [[NAME]] application library provides a helper object:
+ <classname>TitleBuffer</classname>.</para>
+ </sect1>
- <screenshot>
- <mediaobject>
- <imageobject>
- <imagedata contentwidth="40%" fileref="images/html-login.png"
- format="PNG" />
- </imageobject>
- </mediaobject>
- </screenshot>
+ <sect1>
+ <title>Actions</title>
- <para>Log in as 'sven' and a password of 'pass'. This will direct you
- to the start page of the application, a welcome page is displayed and
- links to the application resources are shown across the top. In the
- same ways as for dnd these links give the user access to the objects
- held and used by the system and provide a way for to create new
- objects. Any other object that subsequently appears on the screen
- represents one of those objects.</para>
+ <para>The screen below shows the action menu for the Taxi object, as
+ rendered by the two different user interfaces:</para>
<screenshot>
<mediaobject>
<imageobject>
- <imagedata contentdepth="40%" fileref="images/html-start.png"
+ <imagedata contentwidth="40%" fileref="images/taxi-menu.png"
format="PNG" />
</imageobject>
</mediaobject>
</screenshot>
- <para>For example if the claims link is clicked on within the expenses
- demo the claims service page is shown with the resource actions
- available in a menu on the left hand side of the screen.</para>
-
- <screenshot>
- <screeninfo></screeninfo>
-
- <mediaobject>
- <imageobject>
- <imagedata contentwidth="40%"
- fileref="images/html-exampl-claims.png" />
- </imageobject>
- </mediaobject>
- </screenshot>
+ <para>By default, any <literal moreinfo="none">public</literal>
+ instance methods on an object, included inherited public methods, will
+ be rendered as a user-action. The exceptions to this rule are:</para>
<itemizedlist>
<listitem>
- <para>Object actions can be invoked by clicking on one of the of
- the options from the left hand menu.</para>
- </listitem>
-
- <listitem>
- <para>Resource actions can be invoked clicking on a resource icon
- link in the resource bar and then clicking on a menu item.</para>
- </listitem>
-
- <listitem>
- <para>As objects are created or opened they are shown in an object
- history beneath the resource bar.</para>
+ <para>Any methods that are recognised by [[NAME]] as having a
+ specific intent. We've seen a number of these already, including
+ <literal moreinfo="none">get</literal> and <literal
+ moreinfo="none">set</literal> methods, <literal
+ moreinfo="none">title</literal>, and the methods prefixed by
+ <literal moreinfo="none">modify</literal>, <literal
+ moreinfo="none">clear</literal>, <literal
+ moreinfo="none">validate</literal>, <literal
+ moreinfo="none">disable</literal> and so on. There is a full list
+ of recognised methods included in the applib documentation.</para>
</listitem>
<listitem>
- <para>Similarly the current context, i.e. the current nesting of
- actions is shown beneath the object history.</para>
+ <para>Any methods that the programmer has specified should be
+ hidden from the user, either statically with <literal
+ moreinfo="none">@Hidden</literal>, or dynamically with a <literal
+ moreinfo="none">hide<methodName></literal> method.</para>
</listitem>
</itemizedlist>
- </sect1>
-
- <sect1>
- <title>Running with the Scimpi Viewer</title>
- <para></para>
- </sect1>
+ <para><literal moreinfo="none">private</literal>, <literal
+ moreinfo="none">protected</literal>, and <literal
+ moreinfo="none">static</literal> methods are ignored by [[NAME]]
+ Objects.</para>
- <sect1>
- <title>Running with the Restful Viewer</title>
+ <para>For example, the action 'Copy From' on the Taxi object, is
+ derived from this method on the
+ <classname>AbstractExpenseItem</classname> class (from which
+ <classname>Taxi</classname> inherits):</para>
- <para></para>
- </sect1>
+ <programlisting format="linespecific"> @MemberOrder(sequence="5")
+ public void copyFrom(final ExpenseItem otherItem) {
+ if (belongsToSameClaim(otherItem)) {
+ if (dateIncurred == null) {
+ modifyDateIncurred(otherItem.getDateIncurred());
+ }
+ } else if (getClass().isInstance(otherItem)) {
+ copyAllSameClassFields(otherItem);
+ }
+ }</programlisting>
- <sect1>
- <title>Running with the Wicket Viewer</title>
+ <para>Again, we can see that the method has been marked up with
+ <literal moreinfo="none">@MemberOrder</literal>, which will govern the
+ relative location of this action on the action menu.</para>
- <para></para>
- </sect1>
+ <para>Because the <literal moreinfo="none">copyFrom</literal> method
+ takes a parameter, when the user invokes the corresponding menu action
+ they will be presented with a dialog, wherein each of the parameters
+ may be specified. This is shown below on the two user
+ interfaces:</para>
- <sect1>
- <title>Running the BDD (Concordion) Tests</title>
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%"
+ fileref="images/taxi-copy-dialog.png" format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
- <para></para>
- </sect1>
- </chapter>
+ <para>Editing a dialog is similar to editing an object: though there
+ are differences in the way they are rendered (for example a dialog has
+ an 'OK' button in both the DND and HTML user interfaces). Parameters
+ that take value types (such as <classname>String</classname> or
+ <classname>Date</classname>) are rendered as fields that the user can
+ type into. Where a parameter is a domain object class or interface, as
+ in this case with <classname>ExpenseItem</classname>, then the user
+ must specify an object of that type. In the DND user interface, the
+ user may drag and drop an object into the parameter field. In the HTML
+ user interface, the user is automatically presented with a drop-down
+ list of objects of that type that they have recently viewed. If the
+ desired object doesn't appear, they may go and find the object (by
+ navigating from another object, or using a find method on one of the
+ start points) and then return to the dialog, where the
+ recently-located object should now appear on the list.</para>
- <chapter>
- <title>Developing Isis Applications</title>
+ <para>Adjacent to the <literal moreinfo="none">copyFrom</literal>
+ method on <classname>AbstractExpenseItem</classname> we can also find
+ the following two methods:</para>
- <abstract>
- <para>This chapter describes the general approach to follow that we
- recommend for developing domain-driven applications on Isis.</para>
- </abstract>
+ <programlisting format="linespecific"> public String disableCopyFrom() {
+ return disabledIfLocked();
+ }
- <sect1>
- <title>Where to Start?</title>
+ public String validateCopyFrom(final ExpenseItem otherItem) {
+ if (belongsToSameClaim(otherItem) || (getClass().equals(otherItem.getClass()))) {
+ return null;
+ }
+ return COPY_WARN;
+ }
+ private final static String COPY_WARN = "Cannot copy";</programlisting>
- <para>There's quite a lot to <emphasis>Apache Isis</emphasis>, with
- lots of optional components (which we call alternates, in keeping with
- JSR-299 terminology). To keep things manageable, the Isis
- documentation is scoped closest to where it's relevant.</para>
+ <para><literal moreinfo="none">disableCopyFrom</literal> and <literal
+ moreinfo="none">validateCopyFrom</literal> are other examples of
+ recognised methods (fully documented in the applib documentation).
+ They work in a similar manner to the <literal
+ moreinfo="none">disable<propertyName></literal> and <literal
+ moreinfo="none">validate<propertyName></literal> methods that we
+ have previously seen - in this case disabling the action under certain
+ conditions, and validating the parameters of the action. For both the
+ user-interfaces shown, disabling the action will result in it being
+ greyed-out on the menu. If the entered set of parameters does not pass
+ the validity test, this will be brought to the user's attention when
+ they attempt to execute the action (e.g. by hitting the OK button),
+ along with an explanatory message.</para>
- <para>On the other hand, it can be difficult to know exactly where to
- go in the first place... hence these notes. If you read them from top
- to bottom then you'll (a) have a pretty good idea of the general
- process we recommend for developing domain applications in
- <emphasis>Apache Isis</emphasis> and (b) know where to look for more
- detailed documentation.</para>
+ <para>By default, the user will be required to specify each of the
+ parameters within the dialog. The programmer may, however, use the
+ <literal moreinfo="none">@Optional </literal>annotation in-line (i.e.
+ immediately before any parameter in the method signature) to specify
+ that that parameter may be left empty.</para>
</sect1>
- <sect1 id="sec.ApplicationArchetypes">
- <title>Application Archetypes</title>
+ <sect1>
+ <title>Defining Services</title>
- <para>We tend to structure Apache Isis applications following a
- standard structure, and you can use the
- <emphasis>application</emphasis> Maven archetype (in
- <filename>support/archetypes/application</filename>) to set this up
- for you. Some of the optional plugins also provide their own Maven
- archetypes which you can run afterwards, for example to setup a
- particular new viewer or object store. The idea is that these are run
- alongside your own application, and provide additional Maven
- (sub)modules which you can then reference.</para>
+ <para>As stated previously all the application code consists either of
+ domain objects or services, with the former typically representing the
+ lions share of the code. Now we'll look at the services.</para>
- <para>Going back to the application archetype though, this sets up a
- simple application that can be run out-of-the-box in exploration mode
- (that is, with the in-memory object store), and using one of the two
- main viewers:</para>
+ <para>Services perform two roles in a [[NAME]] application. First,
+ they provide a place to put functionality that cannot be placed on an
+ instance of a domain object, of which the two most obvious examples
+ are:</para>
<itemizedlist>
<listitem>
- <para>the Drag-n-Drop (<acronym>DnD</acronym>) viewer is a
- client-side viewer that renders the domain objects to a desktop
- metaphor.</para>
+ <para>Finding one or more domain objects where you don't have an
+ associated object to navigate from e.g. finding a Customer object
+ by their name or customer number.</para>
</listitem>
<listitem>
- <para>the <acronym>HTML</acronym> viewer is designed to run within
- a webapp, and renders a single domain object per page.</para>
+ <para>Creating a new instance of a domain object class, where you
+ don't have an existing object to create it from. Thus, although
+ you might decide that it makes sense to create a new Order object
+ by means of an action on Customer, you will probably want to be
+ able to create a new Customer object without necessarily having
+ any Order.</para>
</listitem>
</itemizedlist>
- <para>Each of these can be run from the command line (the
- <acronym>HTML</acronym> viewer boots up an instance of Jetty web
- server); the archetype documentation explains how; there's further
- documentation on these particular viewers themselves in their
- respective module documentation (see <xref
- linkend="sec.Viewers" />).</para>
+ <para>To fulfill these requirements we could create two separate
+ services, called, say, CustomerFinder and CustomerFactory. Or we could
+ create a single service called, say, Customers, which has methods to
+ cover both requirements. There's no hard-and-fast rule about how
+ services should be partitioned.</para>
- <para>Later on you'll want to deploy the application more formally,
- for example as a <acronym>WAR</acronym> archive (see <xref
- linkend="sec.DeployingAnIsisApplication" /> for more details).</para>
- </sect1>
+ <para>The second role that services perform within a [[NAME]]
+ application is to bridge domains. The following are examples of what
+ we mean by bridging domains:</para>
- <sect1>
- <title>Programming Model</title>
+ <itemizedlist>
+ <listitem>
+ <para>Linking to functionality that already exists, or has to
+ exist, outside of the [[NAME]] application, such as pre-existing
+ services, or functionality within legacy systems.</para>
+ </listitem>
- <para>Once you've got the archetype application running, you're ready
- to start developing your own domain objects. But no matter what
- application you are developing, you'll need to understand the
- <emphasis>Isis programming model</emphasis>.</para>
+ <listitem>
+ <para>Bridging between technical domains, such as between the
+ object domain and the relational database domain, or the email
+ domain.</para>
+ </listitem>
- <para>The programming model is the set of annotations and programming
- conventions that Isis recognizes. You'll find the programming model is
- documented in the Application Library module (in
- <filename>applib</filename>).</para>
+ <listitem>
+ <para>(Less commonly) Bridging between isolated modelling domains.
+ The [[NAME]] philosophy is to aim, where possible, for a single
+ coherent enterprise object model running within the same
+ application space. Where this is not possible (e.g. for technical
+ or for political reasons), then services may be used to
+ communicate between the domains without requiring common object
+ definitions and/or identities.</para>
+ </listitem>
+ </itemizedlist>
- <para>In addition, the applib contains a small number of utilities
- which can be useful when writing your application: one such is the
- ability to create XML snapshots of your domain object graphs.</para>
+ <para>In this section we'll look at how services are defined, and in
+ the next section at how they are used.</para>
- <para>The applib documentation also provides pointers to other
- features that can have an impact on the way in which you write your
- code, namely the cglib and javassist bytecode providers modules (<xref
- linkend="sec.BytecodeProviders" />), the
- <filename>alternates/headless</filename> viewer plugin (<xref
- linkend="sec.Headless" />), and the <ulink
- url="http://groovy.codehaus.org">Groovy</ulink> language support
- (<xref linkend="sec.OtherLanguages" />).</para>
- </sect1>
+ <para>Services are implemented as Java classes, as are domain objects,
+ but they are handled differently by the framework.</para>
- <sect1>
- <title>Fixtures and Prototyping</title>
+ <para>It is good practice to define services as Java interfaces. That
+ way it is possible for the implementation of the service to change
+ over time, without affecting any of the objects that use the service.
+ During development it is often useful to develop a simple 'mock'
+ implementation of a service that can be used either for prototyping or
+ testing purposes; this can then be replaced with a proper
+ implementation as development progresses towards deployment. For
+ example, within the Expenses Processing application, the following
+ Java interface defines a service for sending an email:</para>
- <para>We suggest that the fastest way to develop your application is
- to start prototyping using the in-memory object store (that is, as set
- up by the application archetype, see <xref
- linkend="sec.ApplicationArchetypes" />). The nice thing about working
- this way is that there is no database schema to slow you down; you can
- make changes and then rapidly try them out.</para>
+ <programlisting format="linespecific"> package org.apache.isis.example.expenses.services;
- <para>On the other hand, the in-memory object store doesn't persist
- objects between runs, so you'll soon tire of continually recreating
- test objects to try out your changes. You should therefore use
- fixtures: blocks of code that are used to setup objects in the
- in-memory object store prior to the app running.</para>
+ public interface EmailSender {
- <para>You can find more information about using fixtures in the
- <filename>applib</filename> module. It's also worth knowing about them
- because they are used when writing tests for your domain application
- (see <xref linkend="sec.AgileTesting" />).</para>
- </sect1>
+ void sendTextEmail(final String toEmailAddress, final String text);
+ }</programlisting>
- <sect1 id="sec.AgileTesting">
- <title>Agile Testing</title>
+ <para>This service definition has just one method, but it could easily
+ have more, such as methods that take a <literal
+ moreinfo="none">List</literal> of recipient addresses, or that can
+ accommodate file attachments. <literal
+ moreinfo="none">JavaMailSender</literal> is an implementation of that
+ service:</para>
- <para><emphasis>Apache Isis</emphasis> is very much aligned to agile
- development practices, and provides two complementary mechanisms for
- you to test-drive the development of your application.</para>
+ <programlisting format="linespecific">public class JavaMailSender extends AbstractService implements EmailSender {
- <sect2>
- <title>Story Testing</title>
+ private static final String SMTP_HOST_NAME = "localhost";
+ private static final String SMTP_AUTH_USER = "expenses@donotreply.org";
+ private static final String SMTP_AUTH_PWD = "";
+ private static final boolean authenticate = false;
- <para>Many agile practitioners use story tests as a means to capture
- the acceptance (or completion) criteria for high-level user stories.
- These story tests are typically captured in a non-programmatic form
- so that is understandable to domain experts as well as
- programmers.</para>
+ private class SMTPAuthenticator extends javax.mail.Authenticator {
+ public PasswordAuthentication getPasswordAuthentication() {
+ final String username = SMTP_AUTH_USER;
+ final String password = SMTP_AUTH_PWD;
+ return new PasswordAuthentication(username, password);
+ }
+ }
- <para>Isis provides integrations with two story testing
- frameworks:</para>
+ public void sendTextEmail(final String toEmailAddress, final String text) {
+ try {
+ final Properties properties = new Properties();
+ properties.put("mail.smtp.host", SMTP_HOST_NAME);
+ properties.put("mail.smtp.auth", authenticate ? "true" : "false");
+ final Authenticator authenticator = authenticate ? new SMTPAuthenticator() : null;
+ final Session session = Session.getDefaultInstance(properties, authenticator);
+ final Message message = new MimeMessage(session);
+ final InternetAddress fromAddress = new InternetAddress(SMTP_AUTH_USER);
+ final InternetAddress toAddress = new InternetAddress(toEmailAddress);
+ message.setFrom(fromAddress);
+ message.setRecipient(Message.RecipientType.TO, toAddress);
+ message.setSubject("Expenses notification");
+ message.setContent(text, "text/plain");
+ Transport.send(message);
+ } catch (AddressException e) {
+ throw new ApplicationException("Invalid email address", e);
+ } catch (MessagingException e) {
+ throw new ApplicationException("Problem sending email", e);
+ }
+ }
+}</programlisting>
- <itemizedlist>
- <listitem>
- <para><ulink url="http://fitnesse.org">FitNesse</ulink>, where
- the story test is captured within a wiki, and</para>
- </listitem>
+ <para>We can see that this service performs a technical bridging role:
+ it bridges between the object domain and an external SMTP
+ server.</para>
- <listitem>
- <para><ulink url="http://concordion.org">Concordion</ulink>,
- where the story test is captured as HTML.</para>
- </listitem>
- </itemizedlist>
+ <para>Since there could be multiple implementations of any one service
+ within our code base, [[NAME]] needs to be informed of which services
+ it is to reference when running an application. This is done within
+ the properties files. For example, the <filename class="directory"
+ moreinfo="none">isis.properties</filename> file, which may be found
+ within the <literal
+ moreinfo="none">expenses.app.client\config</literal> directory,
+ contains the property specification:</para>
- <para>For more information on using these integrations and their
- supporting archetypes, see the <filename>storytests</filename>
- module.</para>
- </sect2>
+ <programlisting format="linespecific">isis.services.prefix=org.apache.isis.example.expenses
+isis.services=services.JavaMailSender</programlisting>
- <sect2 id="sec.UnitTesting">
- <title>Unit Testing</title>
+ <para>This specifies that the class JavaMailSender is to be referenced
+ as a service within the application. You will find a list of other
+ services being referenced there also. Many of those services are
+ 'repositories', and though there is no technical difference between a
+ repository and any other kind of service, repositories play such an
+ important role in [[NAME]] applications, that they are worth exploring
+ in more detail.</para>
- <para>Unlike story tests, unit tests are normally written in a
- programming language, typically in a framework such as <ulink
- url="http://junit.org">JUnit</ulink>. <emphasis>A story test ensures
- that the right system is built, while a unit tests ensures the
- system is built right</emphasis>.</para>
+ <sect2>
+ <title>Repositories</title>
- <para>When writing unit tests, you have a choice. Since all the
- business logic in [[NAME]] applications is encapsulated in domain
- object pojos, you can just write unit tests using nothing more than
- JUnit and perhaps also a mocking library such as <ulink
- url="http://jmock.org">JMock</ulink>.</para>
+ <para>[[NAME]] handles the basic object lifecycle (create, read,
+ update, delete) automatically - there is no need to define your own
+ methods for saving or updating objects, or for retrieving an object
+ that you have a reference to. These mechanisms work the same way
+ irrespective of what technology you are using to persist the objects
+ - such as via Hibernate, natively to a relational database, or via
+ the 'XML Object Store'.</para>
- <para>A slightly more sophisticated approach is to use the JUnit
- integrations and supporting classes of the (so-called) Headless
- module (in <filename>alternates/headless</filename>) . The idea of
- these utilities is to wrap your domain objects in proxies that apply
- the same rules as an <emphasis>Apache Isis</emphasis> viewer. For
- example, if you try to change a property or invoke an action that is
- disabled, then the proxy will throw an exception. You write your
- test to pass if the exception is thrown, and failed otherwise (eg
- using <code>@Test(expected=DisabledException.class)</code>).</para>
- </sect2>
- </sect1>
+ <para>[[NAME]] even provides some simple mechanisms for searching
+ for persisted objects - that also operate the same way, irrespective
+ of the object store. However, a business application will also need
+ more complex search queries that, for reasons of efficiency, will
+ need to be written specifically for the type of object store you are
+ working with.</para>
- <sect1>
- <title>A Domain Library</title>
+ <para>Best practice in application design suggests that such queries
+ should be implemented on 'Repository' classes, rather than within
+ the domain classes directly. That way if you change the persistent
+ object store, you can just create a new implementation of the
+ affected Repositories, without having to change any domain classes.
+ [[NAME]] supports this concept. Within the Expenses application you
+ will find the following three repository definitions:</para>
- <para>The idea behind the <filename>domain</filename> module is to
- provide some off-the-shelf code for you to use and adapt in your own
- applications. This code is fully tested (comes with tests), and is
- intended to be well-designed. Using code from the library should give
- you a kick-start in writing your own domain applications.</para>
+ <programlisting format="linespecific">org.apache.isis.example.expenses.claims.ClaimRepository
+org.apache.isis.example.expenses.employee.EmployeeRepository;
+org.apache.isis.example.expenses.recordedAction.impl.RecordedActionRepository;</programlisting>
- <note>
- <para>The library is currently very modest, but we hope it might
- build up in time.</para>
- </note>
+ <para>In each case the repository is defined as a Java interface,
+ anticipating the possibility of different implementations. We'll
+ look at the <classname>ClaimRepository</classname>
+ definition:</para>
- <para>Of course, there's a limit to the complexity of the code that's
- included in the library, because every domain is different. But having
- a full set of tests should allow you to safely refactor the code to
- your own requirements.</para>
+ <programlisting format="linespecific">public interface ClaimRepository {
+ final static int MAX_CLAIMS = 20;
+ final static int MAX_ITEMS = 10;
- <para>There's also no guarantee that the library will contain code for
- your specific requirement. But if you do write some domain logic that
- you think might be reusable by others, why not consider donating it
- back to Isis when you've done so?</para>
+ List<Claim> findClaims(final Employee employee, final ClaimStatus status, final String description);
- <para>The domain library breaks out into three: services, entities and
- values.</para>
+ List<Claim> findRecentClaims(final Employee employee);
- <sect2>
- <title>Domain Services</title>
+ boolean descriptionIsUniqueForClaimant(final Employee employee, final String initialDescription);
- <para><emphasis>Apache Isis</emphasis> applications use
- <emphasis>domain service</emphasis>s (a domain-driven design
- pattern) to allow objects to interact with other domains (or
- <emphasis>bounded context</emphasis>s, to use the correct term).
- These domain services are automatically injected into each domain
- object as it is instantiated.</para>
+ List<ExpenseItem> findExpenseItemsLike(final ExpenseItem item);
- <para>Domain services split into two: those that interact with
- technical domains (such as sending email, or rendering
- <acronym>PDF</acronym>s), and those that interact with business
- domains (such as general ledger, <acronym>CRM</acronym>s or legacy
- systems).</para>
+ List<Claim> findClaimsAwaitingApprovalBy(Employee approver);
+
+ ClaimStatus findClaimStatus(String title);
- <para>Obviously domain services that bridge to business domain
- services are always likely to be specific to each individual
- application. So the services in the
- <filename>domain/services</filename> module focus on providing
- off-the-shelf implementations for some of the more common
- <emphasis>technical</emphasis> domain services.</para>
+ ExpenseItemStatus findExpenseItemStatus(String title);
+}</programlisting>
- <note>
- <para>TODO: discuss comments on general style; use of
- primitives;</para>
+ <para>This interface defines some seven method signatures for
+ retrieving <classname>Claim</classname>s and
+ <classname>ExpenseItem</classname>s. Note that there is no hard rule
+ about the scope of a single Repository - we could have decided to
+ separate this into a <classname>ClaimRepository</classname> and an
+ <literal moreinfo="none">ExpenseItemRepository</literal> if that
+ offered us some advantage.</para>
- <para></para>
- </note>
+ <para>The example application contains two concrete implementations
+ of <classname>ClaimRepository</classname>:</para>
- <note>
- <para>TODO: discuss fact that different implementations may be
- required if running client/server (it may not even make sense to
- have an implementation that does anything on server, eg preview
- PDF communication in Acrobat Reader)</para>
+ <programlisting format="linespecific">org.apache.isis.example.expenses.services.inmemory.ClaimRepositoryInMemory
+org.apache.isis.example.expenses.services.hibernate.ClaimRepositoryHibernate</programlisting>
- <para></para>
- </note>
- </sect2>
+ <para>The first of these is intended for use with a standalone
+ prototype - with a relatively small number of object instances, all
+ held in memory. So the finder methods can be written 'naively' - to
+ enumerate through all the objects in a class and find the match(es).
+ The following is its implementation of the
+ <methodname>findClaimsAwaitingApprovalBy</methodname> method:</para>
- <sect2>
- <title>Domain Values</title>
+ <programlisting format="linespecific"> public List<Claim> findClaimsAwaitingApprovalBy( final Employee approver ) {
+ return allMatches(
+ Claim.class,
+ new Filter() {
+ public boolean accept(final Object obj) {
+ Claim claim = (Claim) obj;
+ return claim.getStatus().isSubmitted() && claim.getApprover() == approver;
+ }
+ });
+ }</programlisting>
- <para></para>
+ <para>This delegates to an <methodname>allMatches</methodname>
+ method, inherited from
+ <classname>AbstractFactoryAndRepository</classname>, and use a
+ <classname>Filter</classname> object (created in-line) to compare to
+ each instance of <classname>Claim</classname> held in memory. Such
+ methods are very simple to write and debug (because they can invoke
+ methods on the objects being searched, such as
+ <methodname>isSubmitted</methodname> here), but they would not
+ operate efficiently for large numbers of objects.</para>
- <para>integrating 3rd party libraries such as JodaTime.</para>
+ <para><classname>ClaimRepositoryHibernate</classname> is written to
+ work with the Hibernate Object Store and can work efficiently at
+ large scale. Here is its the
+ <methodname>findClaimsAwaitingApprovalBy</methodname> method:</para>
- <para></para>
+ <programlisting format="linespecific">public List<Claim> findClaimsAwaitingApprovalBy( final Employee approver ) {
+ final Criteria criteria = hibernateHelper.createCriteria(Claim.class);
+ criteria.
+ add(Restrictions.eq("approver", approver)).
+ createCriteria("status").
+ add(Restrictions.eq("titleString", ClaimStatus.SUBMITTED));
+ return hibernateHelper.findByCriteria(criteria, Claim.class);
+}</programlisting>
- <note>
- <para>TODO</para>
- </note>
- </sect2>
+ <para>This implementation uses a <classname>Criteria</classname>
+ object, a class provided by the <ulink
+ url="www.hibernate.org">Hibernate</ulink> framework.</para>
- <sect2>
- <title>Domain Entities</title>
+ <para>Both <classname>ClaimRepositoryInMemory</classname> and
+ <classname>ClaimRepositoryHibernate</classname> inherit from
+ <classname>ClaimRepositoryAbstract</classname>, which inherits from
+ <classname>AbstractFactoryAndRepository</classname> and also
+ implements the <classname>ClaimRepository</classname> interface.
+ This pattern is not a requirement - the implementations do not need
+ to inherit from any framework class, they can just implement the
+ required Repository interface natively. However the advantage of
+ this pattern is that some simple query methods can be written
+ generically, as shown in these two examples:</para>
- <para></para>
+ <programlisting format="linespecific">public List<ExpenseItem> findExpenseItemsOfType(final Employee employee, final ExpenseType type) {
+ final List<Claim> claims = findClaims(employee, null, null);
+ final List<ExpenseItem> items = new ArrayList<ExpenseItem>();
+ for (final Claim claim : claims) {
+ ExpenseItem pattern = (ExpenseItem) newTransientInstance((Class) type.correspondingClass());
+ pattern.setClaim(claim);
+ List list = (List) uniqueMatch((Class) type.correspondingClass(), pattern, EXCLUDING_SUBCLASSES);
+ items.addAll(list);
+ }
+ return items;
+}
- <para>This should mostly be an x-ref to the applib documentation.
- However, applib is really just the programming model for the
- "default" programming model. So also a good place to explain the
- concept of programming models, and how they can be adjusted.</para>
+public ClaimStatus findClaimStatus(String title) {
+ return uniqueMatch(ClaimStatus.class, title, EXCLUDING_SUBCLASSES);
+}</programlisting>
- <para></para>
+ <para>These two query methods both delegate to
+ <methodname>uniqueMatch</methodname>, inherited from
+ <classname>AbstractFactoryAndRepository</classname>, but different,
+ overloaded, versions of that method.
+ <methodname>findExpenseItemsOfType</methodname> invokes
+ <methodname>uniqueMatch</methodname> with a pattern - an instance of
+ <classname>ExpenseItem</classname> that has been set up with the
+ fields where a match is required.
+ <methodname>findClaimStatus</methodname> invokes
+ <methodname>uniqueMatch</methodname> with a
+ <classname>String</classname> representing the title of the object
+ required. The implementation of both of these forms of query is
+ delegated to the object store, in a manner that is transparent to
+ the application programmer. So, if the nature of the query can be
+ represented in the form of a find by title, or a find by pattern,
+ then it is advantageous to use these methods on
+ <classname>AbstractFactoryAndRepository</classname>. Otherwise you
+ can write specialised methods on the respective repository
+ implementations.</para>
- <para>... Programming Model</para>
+ <para>As with all services, we need to inform the framework of the
+ existence and intent of these implementations, via the properties
+ files. Within <filename class="directory"
+ moreinfo="none">isis.properties</filename> you will find:</para>
- <para>x-ref applib docs</para>
+ <programlisting format="linespecific">isis.services=services.inmemory.ClaimRepositoryInMemory</programlisting>
- <para></para>
+ <para>and within <filename class="directory"
+ moreinfo="none">persistor_hibernate.properties</filename> you will
+ find:</para>
- <para>... Lazy Loading / Dirty Tracking</para>
+ <programlisting format="linespecific">isis.services = services.hibernate.ClaimRepositoryHibernate</programlisting>
- <para></para>
+ <para><filename class="directory"
+ moreinfo="none">persistor_hibernate.properties</filename> is only
+ referenced if the application is run with the Hibernate Object
+ Store, in which case the framework will recognise that
+ <classname>ClaimRepositoryHibernate</classname> is intended to
+ replace <classname>ClaimRepositoryInMemory</classname> as the
+ implementation to use.</para>
+ </sect2>
- <para></para>
+ <sect2>
+ <title>Factories</title>
- <note>
- <para>TODO</para>
- </note>
+ <para>A Factory is just the name we give to a kind of service that
+ specialises in the creation of new objects, of one or more kinds. It
+ is not necessary to use a Factory in order to create objects within
+ [[NAME]]: we may invoke the methods
+ <methodname>newTransientInstance</methodname> from within a method
+ on a domain object or within any service.</para>
+
+ <para>However, if there is a need to create a type of object from
+ several different places in the application, and there are common
+ steps involved, then it is good practice to delegate this to a
+ Factory. Within [[NAME]] a Factory is just another service, it
+ doesn't have any special status. For example, within the Expenses
+ application, new <classname>Claim</classname>s and new
+ <classname>ExpenseItem</classname>s are created via the
+ <classname>ClaimFactory</classname>. However, new
+ <classname>RecordedAction</classname>s are created in the
+ <classname>RecordedActionService</classname>. Note that
+ <classname>ClaimFactory</classname> is specified as a class rather
+ than an interface, because we have no particular reason to
+ anticipate different implementations of the factory.</para>
</sect2>
</sect1>
<sect1>
- <title>Object Stores</title>
-
- <para>Although you can go a long way in developing your application
- using only fixtures and the in-memory object store, eventually you
- will need to integrate with a "real" object store that persists object
- to some sort of serialized store.</para>
+ <title>Using services</title>
- <para>There are several object stores to choose from. Some are easy to
- configure, some more complex; some are only suitable for single-user
- apps, others for multi-users. Each of the object stores has its own
- documentation, so you can select the correct object store to
- choose:</para>
+ <para>Services are used within [[NAME]] in three ways:</para>
<itemizedlist>
<listitem>
- <para>the <acronym>XML</acronym> object store (in
- <filename>alternates/objectstores/xml</filename>) is designed for
- single-user systems, and persists to its own internal
- (proprietary) <acronym>XML</acronym> format;</para>
- </listitem>
-
- <listitem>
- <para>the Berkeley object store (in
- <filename>alternates/objectstores/berkeley</filename>) is a
- multi-user object store, persists using <ulink
- url="http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html">Berkeley
- DB</ulink>;</para>
- </listitem>
-
- <listitem>
- <para>the <acronym>SQL</acronym> object store (in
- <filename>alternates/objectstores/sql</filename>) is a multi-user
- object store that persists to an <acronym>RDBMS</acronym> (direct
- over <acronym>JDBC</acronym>);</para>
+ <para>Injected into domain objects</para>
</listitem>
<listitem>
- <para>the <acronym>JPA</acronym> object store (in
- <filename>alternates/objectstores/</filename>jpa) is a multi-user
- object store, persists to an <acronym>RDBMS</acronym> (using a
- <acronym>JPA</acronym> provider for the heavy lifting);</para>
+ <para>Directly accessible to the user</para>
</listitem>
<listitem>
- <para>the <acronym>CouchDB</acronym> object store (in
- <filename>alternates/objectstores/</filename>couchdb) is a
- multi-user object store that persists to <ulink
- url="http://couchdb.apache.org">CouchDB</ulink>.</para>
+ <para>To contribute actions to domain objects</para>
</listitem>
</itemizedlist>
- <para>At the time of writing some of these object stores were still
- alpha/beta; check the documentation for each of the above object
- stores to confirm their exact status.</para>
-
- <para>You'll also find coverage of the object store
- <acronym>API</acronym> itself in the <filename>core</filename>
- documentation. Most users of the framework are unlikely to write their
- own object store, of course, though we hope that over time new
- implementations will be written by the community.</para>
- </sect1>
+ <para>We'll look at these three in turn.</para>
- <sect1 id="sec.Viewers">
- <title>Viewers</title>
+ <sect2>
+ <title>Injecting Services into domain objects</title>
- <para>In the same way that object stores provide pluggability for the
- "back-end", <emphasis>Apache Isis</emphasis> also offers pluggability
- on the front-end too.</para>
+ <para>Objects may need access to services, such as repositories for
+ finding related objects, or for calling functionality from outside
+ the domain model. [[NAME]] uses the 'dependency injection' model.
+ Each object merely needs to provide a <literal
+ moreinfo="none">set</literal> method for each type of service that
+ it requires. For example, within the Employee object there is a code
+ region labelled <literal moreinfo="none">Injected
+ Services</literal>:</para>
- <para>We already noted that there are two main viewers, the
- <acronym>DnD</acronym> viewer and the <acronym>HTML</acronym> viewer
- (configured by the application archetype, see <xref
- linkend="sec.ApplicationArchetypes" />). In addition, there are a
- number of other viewers. Here's the full list:</para>
+ <programlisting format="linespecific">// {{ Injected Services
+ // {{ Injected: RecordActionService
+ private RecordActionService recordActionService;
- <itemizedlist>
- <listitem>
- <para>The <acronym>DnD</acronym> viewer (in
- <filename>alternates/viewers/dnd</filename>) is one of the two
- main viewers setup by the application archetype, and is currently
- the only viewer designed to run client-side (ie not as a
- webapp).</para>
+ protected RecordActionService getRecordActionService() {
+ return this.recordActionService;
+ }
- <para>Out-of-the-box it provides a generic view of all domain
- objects, but provides its own API to allow more sophisticated
- views to be constructed.</para>
- </listitem>
+ public void setRecordActionService(final RecordActionService recordActionService) {
+ this.recordActionService = recordActionService;
+ }
+ // }}
- <listitem>
- <para>The <acronym>HTML</acronym> viewer (in
- <filename>alternates/viewers/html</filename>) is the second of the
[... 3977 lines stripped ...]