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 2013/05/22 09:37:26 UTC
svn commit: r1485103 [1/4] - in /isis/site/trunk/content: applib-guide/
applib-guide/DRAFT/ applib-guide/how-tos/ applib-guide/how-tos/images/
applib-guide/images/ applib-guide/recognized-annotations/
applib-guide/recognized-methods/ applib-guide/suppo...
Author: danhaywood
Date: Wed May 22 07:37:24 2013
New Revision: 1485103
URL: http://svn.apache.org/r1485103
Log:
converting applib guide to markdown (wip)
Added:
isis/site/trunk/content/applib-guide/
isis/site/trunk/content/applib-guide/DRAFT/
isis/site/trunk/content/applib-guide/DRAFT/isis-applib-expenses-walkthru.xml
isis/site/trunk/content/applib-guide/applib-guide-intro.md
isis/site/trunk/content/applib-guide/how-tos/
isis/site/trunk/content/applib-guide/how-tos/how-to-01-000-How to write a basic Domain Entity or Service.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-010-How to have a domain entity be a POJO.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-020-How to have a domain service be a POJO.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-030-How to add a property to a domain entity.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-040-How to specify a title for a domain entity.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-050-How to add a collection to a domain entity.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-060-How to add an action to a domain entity or service.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-070-How to specify the icon for a domain entity.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-080-How to specify the order in which properties or collections are displayed.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-090-How to specify the order in which actions appear on the menu.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-100-How to make a property optional.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-110-How to make an action parameter optional.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-120-How to specify the size of String properties.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-130-How to specify the size of String action parameters.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-140-How to specify names or descriptions for an action parameter.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-150-How to inject services into a domain entity or other service.md
isis/site/trunk/content/applib-guide/how-tos/how-to-01-160-How to create or delete objects within your code.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-000-How to add business rules.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-010-How to hide a property.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-020-How to hide a collection.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-030-How to hide an action.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-040-How to specify that none of an object's members is visible.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-050-How to prevent a property from being modified.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-060-How to prevent a collection from being modified.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-070-How to prevent an action from being invoked.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-080-How to specify that none of an object's members can be modified or invoked.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-090-How to specify that an object is immutable.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-100-How to validate user input for a property.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-110-How to validate an object being added or removed from a collection.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-120-How to validate an action parameter argument.md
isis/site/trunk/content/applib-guide/how-tos/how-to-02-130-How to validate declaratively using MustSatisfy.md
isis/site/trunk/content/applib-guide/how-tos/how-to-03-000-How to provide drop-downs and default values.md
isis/site/trunk/content/applib-guide/how-tos/how-to-03-010-How to specify a set of choices for a property.md
isis/site/trunk/content/applib-guide/how-tos/how-to-03-020-How to specify a set of choices for an action parameter.md
isis/site/trunk/content/applib-guide/how-tos/how-to-03-030-How to specify that a class of objects has a limited number of instances.md
isis/site/trunk/content/applib-guide/how-tos/how-to-03-040-How to find an entity (for an action parameter or property) using auto-complete.md
isis/site/trunk/content/applib-guide/how-tos/how-to-03-050-How to specify default values for an action parameter.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-000-How to derive properties and collections and other side effects.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-010-How to make a derived property.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-020-How to make a derived collection.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-030-How to inline the results of a query-only repository action.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-040-How to trigger other behaviour when a property is changed.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-050-How to trigger other behaviour when an object is added or removed.md
isis/site/trunk/content/applib-guide/how-tos/how-to-04-060-How to set up and maintain bidirectional relationships.md
isis/site/trunk/content/applib-guide/how-tos/how-to-05-000-How to provide additional UI hints.md
isis/site/trunk/content/applib-guide/how-tos/how-to-05-010-How to specify a name or description for an object.md
isis/site/trunk/content/applib-guide/how-tos/how-to-05-020-How to specify a name or description for a property.md
isis/site/trunk/content/applib-guide/how-tos/how-to-05-030-How to specify a name or description for a collection.md
isis/site/trunk/content/applib-guide/how-tos/how-to-05-040-How to specify names or description for an action.md
isis/site/trunk/content/applib-guide/how-tos/how-to-05-050-How to specify the icon for an individual objects state.md
isis/site/trunk/content/applib-guide/how-tos/how-to-06-000-How to deal with errors.md
isis/site/trunk/content/applib-guide/how-tos/how-to-06-010-How to pass a messages and errors back to the user.md
isis/site/trunk/content/applib-guide/how-tos/how-to-06-020-How to deal with an unexpected error.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-000-How to handle entity persistence lifecycle.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-010-How to set up the initial value of a property programmatically.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-020-How to insert behaviour into the object life cycle.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-030-How to ensure object is in valid state.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-040-How to specify that an object should not be persisted.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-050-How to perform lazy loading.md
isis/site/trunk/content/applib-guide/how-tos/how-to-07-060-How to perform dirty object tracking.md
isis/site/trunk/content/applib-guide/how-tos/how-to-08-000-How to handle security concerns.md
isis/site/trunk/content/applib-guide/how-tos/how-to-08-020-How to use Isis authorization manager.md
isis/site/trunk/content/applib-guide/how-tos/how-to-080-010-Hiding, disabling or validating for specific users or roles.md
isis/site/trunk/content/applib-guide/how-tos/how-to-09-000-How to write Domain Services, Repositories and Factories.md
isis/site/trunk/content/applib-guide/how-tos/how-to-09-010-How to register domain services, repositories and factories.md
isis/site/trunk/content/applib-guide/how-tos/how-to-09-020-How to write a typical domain service.md
isis/site/trunk/content/applib-guide/how-tos/how-to-09-030-How to use a generic repository.md
isis/site/trunk/content/applib-guide/how-tos/how-to-09-040-How to write a custom repository.md
isis/site/trunk/content/applib-guide/how-tos/how-to-09-050-How to use Factories.md
isis/site/trunk/content/applib-guide/how-tos/images/
isis/site/trunk/content/applib-guide/how-tos/images/AbstractContainedObject-hierarchy.png (with props)
isis/site/trunk/content/applib-guide/images/
isis/site/trunk/content/applib-guide/images/Events.png (with props)
isis/site/trunk/content/applib-guide/images/architecture-perspective.png (with props)
isis/site/trunk/content/applib-guide/images/composition-perspective.png (with props)
isis/site/trunk/content/applib-guide/isis-applib.md
isis/site/trunk/content/applib-guide/recognized-annotations/
isis/site/trunk/content/applib-guide/recognized-annotations/000-about.md
isis/site/trunk/content/applib-guide/recognized-annotations/ActionOrder.md
isis/site/trunk/content/applib-guide/recognized-annotations/ActionSemantics.md
isis/site/trunk/content/applib-guide/recognized-annotations/Aggregated.md
isis/site/trunk/content/applib-guide/recognized-annotations/Audited.md
isis/site/trunk/content/applib-guide/recognized-annotations/AutoComplete.md
isis/site/trunk/content/applib-guide/recognized-annotations/Bounded.md
isis/site/trunk/content/applib-guide/recognized-annotations/Bulk.md
isis/site/trunk/content/applib-guide/recognized-annotations/Debug.md
isis/site/trunk/content/applib-guide/recognized-annotations/Defaulted.md
isis/site/trunk/content/applib-guide/recognized-annotations/DescribedAs.md
isis/site/trunk/content/applib-guide/recognized-annotations/Disabled.md
isis/site/trunk/content/applib-guide/recognized-annotations/Encodable.md
isis/site/trunk/content/applib-guide/recognized-annotations/EqualByContent.md
isis/site/trunk/content/applib-guide/recognized-annotations/Exploration.md
isis/site/trunk/content/applib-guide/recognized-annotations/Facets.md
isis/site/trunk/content/applib-guide/recognized-annotations/FieldOrder.md
isis/site/trunk/content/applib-guide/recognized-annotations/Hidden.md
isis/site/trunk/content/applib-guide/recognized-annotations/Idempotent (deprecated).md
isis/site/trunk/content/applib-guide/recognized-annotations/Ignore (deprecated).md
isis/site/trunk/content/applib-guide/recognized-annotations/Immutable.md
isis/site/trunk/content/applib-guide/recognized-annotations/Mask.md
isis/site/trunk/content/applib-guide/recognized-annotations/MaxLength.md
isis/site/trunk/content/applib-guide/recognized-annotations/MemberGroups.md
isis/site/trunk/content/applib-guide/recognized-annotations/MemberOrder.md
isis/site/trunk/content/applib-guide/recognized-annotations/MultiLine.md
isis/site/trunk/content/applib-guide/recognized-annotations/MustSatisfy.md
isis/site/trunk/content/applib-guide/recognized-annotations/Named.md
isis/site/trunk/content/applib-guide/recognized-annotations/NotContributed.md
isis/site/trunk/content/applib-guide/recognized-annotations/NotInServiceMenu.md
isis/site/trunk/content/applib-guide/recognized-annotations/NotPersistable.md
isis/site/trunk/content/applib-guide/recognized-annotations/NotPersisted.md
isis/site/trunk/content/applib-guide/recognized-annotations/ObjectType.md
isis/site/trunk/content/applib-guide/recognized-annotations/Optional.md
isis/site/trunk/content/applib-guide/recognized-annotations/Paged.md
isis/site/trunk/content/applib-guide/recognized-annotations/Parseable.md
isis/site/trunk/content/applib-guide/recognized-annotations/Plural.md
isis/site/trunk/content/applib-guide/recognized-annotations/Prototype.md
isis/site/trunk/content/applib-guide/recognized-methods/
isis/site/trunk/content/applib-guide/recognized-methods/Recognized Methods and Prefixes.md
isis/site/trunk/content/applib-guide/supporting-features/
isis/site/trunk/content/applib-guide/supporting-features/01-Clock.md
isis/site/trunk/content/applib-guide/supporting-features/02-Profiles.md
isis/site/trunk/content/applib-guide/supporting-features/03-Fixtures and SwitchUser.md
isis/site/trunk/content/applib-guide/supporting-features/04-XML Snapshots.md
isis/site/trunk/content/applib-guide/supporting-features/images/
isis/site/trunk/content/applib-guide/supporting-features/images/Fixtures.png (with props)
isis/site/trunk/content/applib-guide/value-types/
isis/site/trunk/content/applib-guide/value-types/010-intro.md
isis/site/trunk/content/applib-guide/value-types/020-Built-in Value Types.md
isis/site/trunk/content/applib-guide/value-types/030-Custom Value Types.md
isis/site/trunk/content/applib-guide/value-types/04-Third-party Value Types.md
Modified:
isis/site/trunk/content/contributors/release-process.md
Added: isis/site/trunk/content/applib-guide/DRAFT/isis-applib-expenses-walkthru.xml
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/applib-guide/DRAFT/isis-applib-expenses-walkthru.xml?rev=1485103&view=auto
==============================================================================
--- isis/site/trunk/content/applib-guide/DRAFT/isis-applib-expenses-walkthru.xml (added)
+++ isis/site/trunk/content/applib-guide/DRAFT/isis-applib-expenses-walkthru.xml Wed May 22 07:37:24 2013
@@ -0,0 +1,1318 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"file:./src/docbkx/dtd-4.5/docbookx.dtd">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<book>
+ <bookinfo>
+ <title><?eval ${docbkxGuideTitle}?></title>
+
+ <subtitle><?eval ${docbkxGuideSubTitle}?></subtitle>
+
+ <releaseinfo><?eval ${project.version}?></releaseinfo>
+
+ <authorgroup>
+ <author>
+ <firstname>Dan</firstname>
+
+ <surname>Haywood</surname>
+ </author>
+
+ <author>
+ <firstname>Robert</firstname>
+
+ <surname>Matthews</surname>
+ </author>
+ </authorgroup>
+
+ <legalnotice>
+ <para>Permission is granted to make and distribute verbatim copies of
+ this manual provided that the copyright notice and this permission
+ notice are preserved on all copies.</para>
+ </legalnotice>
+ </bookinfo>
+
+ <!-- front matter -->
+
+ <toc></toc>
+
+ <!-- main content -->
+
+ <part>
+ <title>Understanding Apache Isis</title>
+
+ <partintro>
+ <para></para>
+
+ <para></para>
+
+ <para></para>
+ </partintro>
+
+ <chapter id="chp.WhatMakesUpAnIsisApp">
+ <title>What makes up an Isis Application</title>
+
+ <abstract>
+ <para>The code you need to write in order to develop an Isis
+ application.</para>
+ </abstract>
+
+ <para><emphasis>Apache Isis</emphasis> implements the naked objects
+ pattern, and that means that it will automatically provide an
+ object-oriented user interface directly from the domain objects. In this
+ section we will look at that relationship in more detail, with reference
+ to one of the example applications, a simple Expenses Processing
+ application, available in trunk/examples/ supplied as part of the
+ download. As we showed in the previous section, any domain model written
+ for <emphasis>Isis</emphasis> 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>The application code for the Expenses Processing example, like any
+ <emphasis>Isis</emphasis> 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>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>
+
+ <sect1>
+ <title>Domain objects</title>
+
+ <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 Isis handles domain objects, we'll start by looking at
+ an Employee object:</para>
+
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%"
+ fileref="images/employee-views.png" format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
+
+ <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>
+
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%" fileref="images/employee-code.png"
+ format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
+
+ <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>Secondly, we can see that <classname>Employee</classname>
+ extends <classname>AbstractDomainObject</classname> - a class provided
+ within the Isis 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>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>
+
+ <sect1>
+ <title>Properties</title>
+
+ <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>
+
+ <programlisting condition="" format="linespecific">// {{ Name
+ private String name;
+
+ @MemberOrder(sequence="1")
+ @Disabled
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+// }}</programlisting>
+
+ <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>
+
+ <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>The <literal moreinfo="none">getName</literal> has been marked
+ up with two Java annotations, both defined in the Isis application
+ library. Annotations allow the programmer to enrich the information
+ available to the framework. On properties, any Isis annotations are
+ always associated with the <literal moreinfo="none">get</literal>
+ method. However, annotations are not mandatory - you can write a
+ simple Isis application without using any annotations at all.</para>
+
+ <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><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>The next region of the code contains another <literal
+ moreinfo="none">String</literal> property, called <literal
+ moreinfo="none">UserName</literal>:</para>
+
+ <programlisting format="linespecific">// {{ UserName field
+private String userName;
+
+@Hidden
+public String getUserName() {
+ return userName;
+}
+
+public void setUserName(final String variable) {
+ this.userName = variable;
+}
+// }}</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>Next we'll look at the <literal
+ moreinfo="none">EmailAddress</literal> region:</para>
+
+ <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>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>
+
+ <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 Isis 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>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/employee-email-invalid.png"
+ format="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. Isis 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>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>
+
+ <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>
+
+ <para>Next we will look at the <literal
+ moreinfo="none">NormalApprover</literal> region:</para>
+
+ <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>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. Isis 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>
+
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%"
+ fileref="images/employee-approver-views.png"
+ format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
+
+ <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>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>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>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 Isis is administered externally to the application
+ and does not require any coding within the domain objects.)</para>
+ </sect1>
+
+ <sect1>
+ <title>Title</title>
+
+ <para>In the next screen we will look at the <literal
+ moreinfo="none">title</literal> region of the Employee object.</para>
+
+ <programlisting format="linespecific">// {{ Title
+ public String title() {
+ return getName();
+ }
+// }}</programlisting>
+
+ <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. Isis 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, Apache Isis 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 Isis application library provides a helper
+ object: <classname>TitleBuffer</classname>.</para>
+ </sect1>
+
+ <sect1>
+ <title>Actions</title>
+
+ <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 contentwidth="40%" fileref="images/taxi-menu.png"
+ format="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>Any methods that are recognised by Isis 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>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>
+
+ <para><literal moreinfo="none">private</literal>, <literal
+ moreinfo="none">protected</literal>, and <literal
+ moreinfo="none">static</literal> methods are ignored by Apache
+ Isis.</para>
+
+ <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>
+
+ <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>
+
+ <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>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>
+
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata contentwidth="40%"
+ fileref="images/taxi-copy-dialog.png" format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
+
+ <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>
+
+ <para>Adjacent to the <literal moreinfo="none">copyFrom</literal>
+ method on <classname>AbstractExpenseItem</classname> we can also find
+ the following two methods:</para>
+
+ <programlisting format="linespecific"> public String disableCopyFrom() {
+ return disabledIfLocked();
+ }
+
+ 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><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>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>
+ <title>Defining Services</title>
+
+ <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>Services perform two roles in an Isis 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>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>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>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>The second role that services perform within an Isis application
+ is to bridge domains. The following are examples of what we mean by
+ bridging domains:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>Linking to functionality that already exists, or has to
+ exist, outside of the Isis application, such as pre-existing
+ services, or functionality within legacy systems.</para>
+ </listitem>
+
+ <listitem>
+ <para>Bridging between technical domains, such as between the
+ object domain and the relational database domain, or the email
+ domain.</para>
+ </listitem>
+
+ <listitem>
+ <para>(Less commonly) Bridging between isolated modelling domains.
+ The Isis 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 this section we'll look at how services are defined, and in
+ the next section at how they are used.</para>
+
+ <para>Services are implemented as Java classes, as are domain objects,
+ but they are handled differently by the framework.</para>
+
+ <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>
+
+ <programlisting format="linespecific"> package org.apache.isis.example.expenses.services;
+
+ public interface EmailSender {
+
+ void sendTextEmail(final String toEmailAddress, final String text);
+ }</programlisting>
+
+ <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>
+
+ <programlisting format="linespecific">public class JavaMailSender extends AbstractService implements EmailSender {
+
+ 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;
+
+ 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);
+ }
+ }
+
+ 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>
+
+ <para>We can see that this service performs a technical bridging role:
+ it bridges between the object domain and an external SMTP
+ server.</para>
+
+ <para>Since there could be multiple implementations of any one service
+ within our code base, Isis 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>
+
+ <programlisting format="linespecific">isis.services.prefix=org.apache.isis.example.expenses
+isis.services=services.JavaMailSender</programlisting>
+
+ <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 Isis applications, that they are worth exploring in
+ more detail.</para>
+
+ <sect2>
+ <title>Repositories</title>
+
+ <para>Isis 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>Isis 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>
+
+ <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.
+ Isis supports this concept. Within the Expenses application you will
+ find the following three repository definitions:</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>
+
+ <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>
+
+ <programlisting format="linespecific">public interface ClaimRepository {
+ final static int MAX_CLAIMS = 20;
+ final static int MAX_ITEMS = 10;
+
+ List<Claim> findClaims(final Employee employee, final ClaimStatus status, final String description);
+
+ List<Claim> findRecentClaims(final Employee employee);
+
+ boolean descriptionIsUniqueForClaimant(final Employee employee, final String initialDescription);
+
+ List<ExpenseItem> findExpenseItemsLike(final ExpenseItem item);
+
+ List<Claim> findClaimsAwaitingApprovalBy(Employee approver);
+
+ ClaimStatus findClaimStatus(String title);
+
+ ExpenseItemStatus findExpenseItemStatus(String title);
+}</programlisting>
+
+ <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>The example application contains two concrete implementations
+ of <classname>ClaimRepository</classname>:</para>
+
+ <programlisting format="linespecific">org.apache.isis.example.expenses.services.inmemory.ClaimRepositoryInMemory
+org.apache.isis.example.expenses.services.hibernate.ClaimRepositoryHibernate</programlisting>
+
+ <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>
+
+ <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>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><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>
+
+ <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>
+
+ <para>This implementation uses a <classname>Criteria</classname>
+ object, a class provided by the <ulink
+ url="www.hibernate.org">Hibernate</ulink> framework.</para>
+
+ <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>
+
+ <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;
+}
+
+public ClaimStatus findClaimStatus(String title) {
+ return uniqueMatch(ClaimStatus.class, title, EXCLUDING_SUBCLASSES);
+}</programlisting>
+
+ <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>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>
+
+ <programlisting format="linespecific">isis.services=services.inmemory.ClaimRepositoryInMemory</programlisting>
+
+ <para>and within <filename class="directory"
+ moreinfo="none">persistor_hibernate.properties</filename> you will
+ find:</para>
+
+ <programlisting format="linespecific">isis.services = services.hibernate.ClaimRepositoryHibernate</programlisting>
+
+ <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>
+
+ <sect2>
+ <title>Factories</title>
+
+ <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
+ Isis: 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 Isis 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>Using services</title>
+
+ <para>Services are used within Isis in three ways:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>Injected into domain objects</para>
+ </listitem>
+
+ <listitem>
+ <para>Directly accessible to the user</para>
+ </listitem>
+
+ <listitem>
+ <para>To contribute actions to domain objects</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>We'll look at these three in turn.</para>
+
+ <sect2>
+ <title>Injecting Services into domain objects</title>
+
+ <para>Objects may need access to services, such as repositories for
+ finding related objects, or for calling functionality from outside
+ the domain model. Isis 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>
+
+ <programlisting format="linespecific">// {{ Injected Services
+ // {{ Injected: RecordActionService
+ private RecordActionService recordActionService;
+
+ protected RecordActionService getRecordActionService() {
+ return this.recordActionService;
+ }
+
+ public void setRecordActionService(final RecordActionService recordActionService) {
+ this.recordActionService = recordActionService;
+ }
+ // }}
+
+ // {{ Injected: UserFinder
+ private UserFinder userFinder;
+
+ protected UserFinder getUserFinder() {
+ return this.userFinder;
+ }
+
+ public void setUserFinder(final UserFinder userFinder) {
+ this.userFinder = userFinder;
+ }
+ // }}
+// }}</programlisting>
+
+ <para>In this case, the Employee object has specified that it
+ requires two services to be injected: a <literal
+ moreinfo="none">RecordActionService</literal> and a <literal
+ moreinfo="none">UserFinder</literal>. Whenever an instance of
+ Employee is created, or retrieved from the object store, Apache Isis
+ will inject the implementation that it knows about (as specified in
+ properties) for each type of service required. Note that, unlike the
+ other properties we have looked at, <literal
+ moreinfo="none">get</literal> methods may be <literal
+ moreinfo="none">protected</literal>, because the property is not
+ displayed. (Strictly speaking a <literal
+ moreinfo="none">get</literal> is often not needed here - as the
+ injected service may be accessed via the variable - but it is
+ considered to be good practice.)</para>
+
+ <para>From within the object we can then call any of the methods
+ defined for those types of service. For example, we can see that the
+ <methodname>hideEmailAddress</methodname> method makes a call (via
+ <methodname>employeeIsCurrentUser</methodname>) to the
+ <classname>UserFinder</classname> service:</para>
+
+ <programlisting format="linespecific">public boolean hideEmailAddress() {
+ return !employeeIsCurrentUser();
+}
+
+private boolean employeeIsCurrentUser() {
+ return getUserFinder().currentUserAsObject() == this;
+}</programlisting>
+ </sect2>
+
+ <sect2>
+ <title>Making services directly accessible to the user</title>
+
+ <para>Services may be made available directly to the user. On the
+ DND user interface these appear as the large icons on the desktop;
+ on the HTML user interface (that is, as styled by the default CSS)
+ these appear as the tabs across the top of the screen. Which
+ services are made available to a particular user are defined in
+ 'perspectives' within a user profile. Within the Fixture project the
+ class <classname>ExplorationUserProfileFixture</classname> defines
+ the perspectives for various defined prototype users:</para>
+
+ <programlisting format="linespecific">public class ExplorationUserProfileFixture extends UserProfileFixture {
+
+ @Override
+ protected void installProfiles() {
+ ...
+ Profile svenProfile = newUserProfile();
+ Perspective claimsPerspective = svenProfile.newPerspective("Claims");
+ claimsPerspective.addToServices(Claims.class);
+ claimsPerspective.addToServices(Employees.class);
+ saveForUser("sven", svenProfile);
+ ...
+
+ }
+}</programlisting>
+
+ <para>The above example specifies that the user 'sven' is to be
+ given a perspective called 'Claims', which gives him direct access
+ to two services: <classname>ClaimStartPoints</classname> and
+ <classname>EmployeeStartPoints</classname>. If we look at the second
+ of those, we can see that it defines two actions:
+ <methodname>findEmployeeByName</methodname> and
+ <methodname>me</methodname>:</para>
+
+ <programlisting format="linespecific"> @Named("Employees")
+ public class EmployeeStartPoints extends AbstractService {
+ // {{ Title & ID
+
+ // {{ Injected Services
+
+ @MemberOrder(sequence = "2")
+ public List<Employee> findEmployeeByName(@Named("Name (or start of Name)")
+ final String name) {
+ List<Employee> results = employeeRepository.findEmployeeByName(name);
+ if (results.isEmpty()) {
+ warnUser("No employees found matching name: " + name);
+ return null;
+ }
+ return results;
+ }
+
+ @Executed(Executed.Where.LOCALLY)
+ public Employee me() {
+ Employee me = employeeRepository.me();
+ if (me == null) {
+ warnUser("No Employee representing current user");
+ }
+ return me;
+ }
+ }</programlisting>
+
+ <para>Both of these methods delegate to methods on the
+ <classname>EmployeeRepository</classname>, which has been injected
+ (services may be injected into other services, just as into domain
+ objects). Note that it is not necessary to define specific services
+ to be provided directly to the user - we could provide the user with
+ direct access to the Repositories, Factories or other services
+ specified within the application. Creating dedicated user-oriented
+ service definitions just helps us to separate the concerns. Calling
+ them 'Start Points' is also just a convention.</para>
+ </sect2>
+
+ <sect2>
+ <title>Using services to contribute actions to domain
+ objects</title>
+
+ <para>The screens below show the action menu on the Claim object, as
+ rendered by the two different user interfaces:</para>
+
+ <screenshot>
+ <mediaobject>
+ <imageobject>
+ <imagedata align="center" contentwidth="40%"
+ fileref="images/claim-contributed-actions.png"
+ format="PNG" />
+ </imageobject>
+ </mediaobject>
+ </screenshot>
+
+ <para>This menu has a sub-menu, entitled 'Recorded Actions',
+ containing, in this case, a single method 'All Recorded Actions'.
+ Sub-menus in Isis are 'contributed' by services; the actions in the
+ sub-menus are described as 'contributed actions'. In this case the
+ actions are contributed the service
+ <classname>RecordedActionContributedActions</classname>:</para>
+
+ <programlisting format="linespecific"> @Named("Recorded Actions")
+ public class RecordedActionContributedActions extends AbstractService {
+
+ // {{ Injected Services
+
+ public List<RecordedAction> allRecordedActions(RecordedActionContext context) {
+ return recordedActionRepository.allRecordedActions(context);
+ }
+ }</programlisting>
+
+ <para>The method <methodname>allRecordedActions</methodname> takes a
+ <classname>RecordedActionContext</classname> as a parameter, and
+ will return all the <classname>RecordedAction</classname>s
+ associated with that object. Note that
+ <classname>RecordedActionContext</classname> is an interface that
+ defines no methods - it is purely a type definition:</para>
+
+ <programlisting format="linespecific">public interface RecordedActionContext {
+}</programlisting>
+
+ <para>However, this interface is implemented by two classes:
+ <classname>Employee</classname> and <classname>Claim</classname>.
+ The net result of this is that the action 'All Recorded Actions'
+ will be contributed to each instance of
+ <classname>Employee</classname> and of <classname>Claim</classname>.
+ By default, this would appear in a sub-menu named after the service
+ on which the method was defined (i.e. 'Recorded Action Contributed
+ Actions'), but in this case we have used the <literal
+ moreinfo="none">@Named</literal> annotation to override this and
+ render the service name, and hence the sub-menu name, simply as
+ 'Recorded Actions'.</para>
+
+ <para>We can also see that this method delegates its execution to
+ the <classname>RecordedActionRepository</classname>, which has been
+ injected as a service. You are not required to follow this pattern,
+ or this naming convention. In fact, if the
+ <methodname>allRecordedActions</methodname> method on
+ <classname>RecordedActionRepository</classname> was not
+ <classname>@Hidden</classname>, then it would have been contributed
+ automatically - without the need for defining
+ <classname>RecordedActionContributedActions</classname>. We have
+ defined the latter purely to help convey intent and manage our code
+ base.</para>
+
+ <para>The rule is that any method defined on any service that the
+ user is authorised to access (see <xref
+ linkend="chp.SecurityApi" />) and is not hidden, will be contributed
+ to any object of a type that features as any of the parameters to
+ that method.</para>
+
+ <para>This is a very powerful feature of Isis, but it is one that
+ takes a bit of getting used to. In some respects it is a little bit
+ like Aspect Oriented Programming (AOP), in that it allows an object
+ effectively to inherit capabilities from several different sources.
+ However, this all takes place at run-time, not at compile
+ time.</para>
+
+ <para>In a more complex application, it might well be that a domain
+ object might have several contributed sub-menus, each containing
+ several methods. Designing an application this way allows us to keep
+ the model well partitioned. In this very simple example, it has
+ allowed us to keep the part of the model concerned with recording
+ actions very separate from the other parts of the model.</para>
+ </sect2>
+ </sect1>
+
+ <sect1>
+ <title>Fixtures</title>
+
+ <para>Fixtures are used to set up objects within the code based,
+ principally for use within prototyping and or testing. Isis provides
+ specific support for using fixtures. The following code shows a
+ fixture class that sets up one claim:</para>
+
+ <programlisting format="linespecific">public class SvenClaim1NewStatus extends AbstractClaimFixture {
+
+ public static Employee SVEN;
+ public static Employee DICK;
+ public static Claim SVEN_CLAIM_1;
+
+ @Override
+ public void install() {
+ SVEN = EmployeeFixture.SVEN;
+ DICK = EmployeeFixture.DICK;
+
+ SVEN_CLAIM_1 =createNewClaim(SVEN, DICK, "28th Mar - Sales call, London", ProjectCodeFixture.CODE1, new Date(2007,4,3));
+ Date mar28th = new Date(2007,3,28);
+ addTaxi(SVEN_CLAIM_1, mar28th, null, 8.50, "Euston", "Mayfair", false);
+ addMeal(SVEN_CLAIM_1, mar28th, "Lunch with client", 31.90);
+ addTaxi(SVEN_CLAIM_1, mar28th, null, 11.00, "Mayfair", "City", false);
+ }
+}</programlisting>
+
+ <para>This inherits from <classname>AbstractClaimFixture</classname>,
+ which provides the helper methods such as
+ <methodname>createNewClaim</methodname>, and which inherits in turn
+ from <classname>AbstractFixture</classname>, a class in the Isis
+ application library. However, there is no need to follow this pattern:
+ a fixture may be any class that has an
+ <methodname>install</methodname> method.</para>
+
+ <para>Fixtures may be composite, as we can see in this example:</para>
+
+ <programlisting format="linespecific">public class SvenClaims_All extends AbstractClaimFixture {
+
+ public SvenClaims_All() {
+ addFixture(new SvenClaim1NewStatus());
+ addFixture(new SvenClaim2Submitted());
+ addFixture(new SvenClaim5New());
+ addFixture(new SvenClaim3Returned());
+ addFixture(new SvenClaim4Approved());
+ }
+
+ public void install() {}
+}</programlisting>
+
+ <para>This fixture has had five other fixtures added to it. The
+ <methodname>install</methodname> method is empty: Isis will
+ automatically call <methodname>install</methodname> on each of the
+ fixtures that has been added to this composite fixture. This pattern
+ makes it easy to manage large fixtures, and multiple sets of
+ (potentially overlapping) fixtures, both for prototyping and for
+ testing.</para>
+
+ <para>As with services, Isis needs to be instructed which fixtures it
+ should use when running an application. This may be done in the
+ properties files, for example:</para>
+
+ <programlisting format="linespecific">isis.fixtures.prefix=org.apache.isis.example.expenses.fixtures
+isis.fixtures=ExplorationPerspectiveFixture, RefdataFixture, EmployeeFixture, SvenClaims_All</programlisting>
+
+ <para>Note that this also specifies the
+ <classname>ExplorationPerspectiveFixture</classname>, which we looked
+ at earlier.</para>
+
+ <para>Fixtures may also be specified as a command line parameter (see
+ <xref linkend="sec.RuntimeLauncher" />) when launching the application
+ from the command line; composite fixtures are especially handy in this
+ circumstance.</para>
+ </sect1>
+ </chapter>
+ </part>
+</book>
Added: isis/site/trunk/content/applib-guide/applib-guide-intro.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/applib-guide/applib-guide-intro.md?rev=1485103&view=auto
==============================================================================
--- isis/site/trunk/content/applib-guide/applib-guide-intro.md (added)
+++ isis/site/trunk/content/applib-guide/applib-guide-intro.md Wed May 22 07:37:24 2013
@@ -0,0 +1,40 @@
+Apache Isis Programming Model
+=======
+
+*Apache Isis* is designed to allow programmers rapidly develop domain-driven applications following the [Naked
+Objects](http://en.wikipedia.org/wiki/Naked_Objects) pattern. It is made
+up of a core plus a number of components for each of the main APIs:
+objectstores, security, viewers and profilestores.
+
+This guide is written for programmers looking to understand the
+programming conventions, annotations and supporting utilities within the
+*Apache Isis* application library (or *applib*), in order that the
+framework can correctly pick up and render the business rules and logic
+encoded within their domain objects.
+
+*Apache Isis* is hosted at the [Apache
+Foundation](http://incubator.apache.org/isis), and is licensed under
+[Apache Software License
+v2](http://www.apache.org/licenses/LICENSE-2.0.html).
+
+The conventions of the programming model are best described as
+'intentional' - they convey an intention as to how domain objects, their
+properties and behaviours, are to be made available to users. The
+specific way in which those intentions are interpreted or implemented
+will depend upon the framework, and/or the particular components or
+options selected within that framework.
+
+To pick a single example, marking up a domain class with the annotation
+`@Bounded` is an indication that the class is intended to have only a
+small number of instances and that the set does not change very often -
+such as the class `Country`. This is an indication to a viewer, for
+example, that the whole set of instances might be offered to the user in
+a convenient form such as a drop-down list. The programming convention
+has *not* been defined as `@DropDownList` because any equivalent
+mechanism will suffice: a viewer might not support drop-down-lists but
+instead might provide a capability to select from an `@Bounded` class by
+typing the initial letters of the desired instance.
+
+This part of the guide is a set of chapters that provides how-to's for
+writing domain objects, by which we mean domain entities, value types,
+services and repositories/factories.
Added: isis/site/trunk/content/applib-guide/how-tos/how-to-01-000-How to write a basic Domain Entity or Service.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/applib-guide/how-tos/how-to-01-000-How%20to%20write%20a%20basic%20Domain%20Entity%20or%20Service.md?rev=1485103&view=auto
==============================================================================
--- isis/site/trunk/content/applib-guide/how-tos/how-to-01-000-How to write a basic Domain Entity or Service.md (added)
+++ isis/site/trunk/content/applib-guide/how-tos/how-to-01-000-How to write a basic Domain Entity or Service.md Wed May 22 07:37:24 2013
@@ -0,0 +1,12 @@
+How to write a basic Domain Entity or Service
+=============================================
+
+> How-to write a basic domain entity or service, specifying its
+> properties, collections and actions, and using some of the most
+> commonly-used additional semantics.
+
+Domain entities are instances of some class, usually (the vast majority)
+being persisted. Domain services are singletons that act typically act
+as factories and repositories. Domain entities have state in the form of
+properties and collections; domain services do not. Both domain entities
+and services have behaviour, in the form of actions.
Added: isis/site/trunk/content/applib-guide/how-tos/how-to-01-010-How to have a domain entity be a POJO.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/applib-guide/how-tos/how-to-01-010-How%20to%20have%20a%20domain%20entity%20be%20a%20POJO.md?rev=1485103&view=auto
==============================================================================
--- isis/site/trunk/content/applib-guide/how-tos/how-to-01-010-How to have a domain entity be a POJO.md (added)
+++ isis/site/trunk/content/applib-guide/how-tos/how-to-01-010-How to have a domain entity be a POJO.md Wed May 22 07:37:24 2013
@@ -0,0 +1,17 @@
+How to have a domain entity be a POJO (not inherit from framework superclasses)
+-------------------------------------------------------------------------------
+
+It isn't mandatory for domain entities to inherit from any framework
+superclass; they can be plain old java objects (pojos) if required.
+However, they do at a minimum need to have a
+`org.apache.isis.applib.DomainObjectContainer` injected into them (an
+interface), from which other framework services can be accessed.
+
+If you don't have a requirement to inherit from any other superclass,
+then it usually makes sense to inherit from mention
+`org.apache.isis.applib.AbstractDomainObject`, which already supports the
+`DomainObjectContainer` and has a number of convenience helper methods.
+
+<!--
+There is further coverage of DomainObjectContainer in ? and also in ?.
+-->
Added: isis/site/trunk/content/applib-guide/how-tos/how-to-01-020-How to have a domain service be a POJO.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/applib-guide/how-tos/how-to-01-020-How%20to%20have%20a%20domain%20service%20be%20a%20POJO.md?rev=1485103&view=auto
==============================================================================
--- isis/site/trunk/content/applib-guide/how-tos/how-to-01-020-How to have a domain service be a POJO.md (added)
+++ isis/site/trunk/content/applib-guide/how-tos/how-to-01-020-How to have a domain service be a POJO.md Wed May 22 07:37:24 2013
@@ -0,0 +1,23 @@
+How to have a domain service be a POJO (not inherit from framework superclasses)
+--------------------------------------------------------------------------------
+
+Like entities, it isn't mandatory for domain services to inherit from
+any framework superclass; they can be plain-old pojos if required.
+However, again, like entities, they do at a minimum need to have a
+org.apache.isis.applib.DomainObjectContainer injected into them (an
+interface), from which other framework services can be accessed.
+
+If you don't have a requirement to inherit from any other superclass,
+then it usually makes sense to inherit from one of the abstract classes
+in the applib, either org.apache.isis.applib.AbstractService or
+org.apache.isis.applib.AbstractRepositoryAndFactory. These already
+supports the DomainObjectContainer and have a number of convenience
+helper methods.
+
+The UML class diagram below shows the relationship between these types
+and the DomainObjectContainer.
+
+![](images/AbstractContainedObject-hierarchy.png)
+
+What this means is that *Apache Isis* treats factories and repositories
+as just another type of domain service.
Added: isis/site/trunk/content/applib-guide/how-tos/how-to-01-030-How to add a property to a domain entity.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/applib-guide/how-tos/how-to-01-030-How%20to%20add%20a%20property%20to%20a%20domain%20entity.md?rev=1485103&view=auto
==============================================================================
--- isis/site/trunk/content/applib-guide/how-tos/how-to-01-030-How to add a property to a domain entity.md (added)
+++ isis/site/trunk/content/applib-guide/how-tos/how-to-01-030-How to add a property to a domain entity.md Wed May 22 07:37:24 2013
@@ -0,0 +1,50 @@
+How to add a property to a domain entity
+----------------------------------------
+
+A property is a scalar attribute or field of a domain entity. Its type
+can be either a value type (such as an int, Date or String), or a
+reference to another entity.
+
+Properties are specified using the JavaBean conventions, recognizing a
+standard accessor/mutator pair (`get` and `set`).
+
+The syntax is:
+
+ public PropertyType getPropertyName()
+
+ public void setPropertyName(PropertyType param)
+
+where `PropertyType` is a primitive, a value object or an entity object.
+
+Properties may either be for a value type or may reference another
+entity. Values include Java primitives, and JDK classes with value
+semantics (eg `java.lang.Strings` and `java.util.Dates`; see ? for the
+full list). It is also possible to write your own value types (see ?). A
+property referencing another domain object is sometimes called an
+association.
+
+For example, the following example contains both a value (`String`)
+property and a reference (`Organisation`) property:
+
+ public class Customer {
+
+ private String firstName;
+ public String getFirstName() {
+ return firstName;
+ }
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+
+ private Organisation organisation;
+ public Organisation getOrganisation() {
+ return organisation;
+ }
+ public void setOrganisation(Organisation organisation) {
+ this.organisation = organisation;
+ }
+
+ ...
+ }
+