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 2012/12/21 15:13:51 UTC
[2/4] ISIS-289: updated and restructured applib docs
http://git-wip-us.apache.org/repos/asf/isis/blob/17870ebd/core/applib/src/docbkx/guide/isis-applib.xml
----------------------------------------------------------------------
diff --git a/core/applib/src/docbkx/guide/isis-applib.xml b/core/applib/src/docbkx/guide/isis-applib.xml
index 28ccc04..e4ddeb1 100644
--- a/core/applib/src/docbkx/guide/isis-applib.xml
+++ b/core/applib/src/docbkx/guide/isis-applib.xml
@@ -59,7 +59,7 @@
rapidly develop domain-driven applications following the <ulink
url="http://en.wikipedia.org/wiki/Naked_Objects">Naked Objects</ulink>
pattern. It is made up of a core plus a number of components for each of
- the main APIs: objectstores, security, viewers and profilestores. </para>
+ the main APIs: objectstores, security, viewers and profilestores.</para>
<para>This guide is written for programmers looking to understand the
programming conventions, annotations and supporting utilities within the
@@ -106,21 +106,24 @@
</partintro>
<chapter id="chp.Objects">
- <title>Domain Entities</title>
+ <title>How to write a basic Domain Entity or Service</title>
<abstract>
- <para>How-to's relating to the domain objects, specifically domain
- entities.</para>
+ <para>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.</para>
</abstract>
- <para>This chapter contains how-to's for programming conventions that
- apply to the domain objects, or more specifically domain entities.
- Domain values conventions are covered in <xref
- linkend="chp.ValueTypes" />.</para>
+ <para>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.</para>
<sect1>
- <title>How to have entities be POJOs (not inherit from framework
- superclasses)</title>
+ <title>How to have a domain entity be a POJO (not inherit from
+ framework superclasses)</title>
<para>It isn't mandatory for domain entities to inherit from any
framework superclass; they can be plain old java objects (pojos) if
@@ -142,8 +145,49 @@
linkend="chp.DomainServices" />.</para>
</sect1>
+ <sect1 id="sec.AbstractService">
+ <title>How to have a domain service be a POJO (not inherit from
+ framework superclasses)</title>
+
+ <para>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
+ <classname>org.apache.isis.applib.DomainObjectContainer</classname>
+ injected into them (an interface), from which other framework services
+ can be accessed.</para>
+
+ <para>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
+ <classname>org.apache.isis.applib.AbstractService</classname> or
+ <classname>org.apache.isis.applib.AbstractRepositoryAndFactory</classname>.
+ These already supports the
+ <classname>DomainObjectContainer</classname> and have a number of
+ convenience helper methods.</para>
+
+ <para>The <acronym>UML</acronym> class diagram below shows the
+ relationship between these types and the
+ <classname>DomainObjectContainer</classname>.</para>
+
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/AbstractContainedObject-hierarchy.png"
+ scale="65" />
+ </imageobject>
+ </mediaobject>
+
+ <para>What this means is that <emphasis>Apache Isis</emphasis> treats
+ factories and repositories as just another type of domain
+ service.</para>
+ </sect1>
+
<sect1>
- <title>How to add a property to an object</title>
+ <title>How to add a property to a domain entity</title>
+
+ <para>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.</para>
<para>Properties are specified using the JavaBean conventions,
recognizing a standard accessor/mutator pair (<code>get</code> and
@@ -196,7 +240,12 @@ public void setPropertyName(PropertyType param)</programlisting></para>
</sect1>
<sect1>
- <title>How to add a collection to an object</title>
+ <title>How to add a collection to a domain entity</title>
+
+ <para>A collection is a multi-valued attribute/field of a entity, in
+ other words a <code>List</code> or a <code>Set</code>, containing
+ references to other domain entities. <code>Map</code>s are not
+ supported. Collections of value types are not supported.</para>
<para>A collection is recognized via an accessor/mutator method pair
(<code>get</code> and set) for any type of collection provided by the
@@ -256,7 +305,14 @@ public class Department {
</sect1>
<sect1>
- <title>How to add an action to an object</title>
+ <title>How to add an action to a domain entity or service</title>
+
+ <para>An 'action' is a method that we expect the user to be able to
+ invoke on a domain entity via the user interface, though it may also
+ be invoked programmatically within the object model. The following
+ conventions are used to determine when and how methods are made
+ available to the user as actions, and the means by which a user can
+ interact with those actions</para>
<para>By default, any <literal moreinfo="none">public</literal>
instance method that you add to a class will be treated as a user
@@ -305,7 +361,8 @@ public class Department {
</sect1>
<sect1>
- <title condition="java">How to specify a title for an object</title>
+ <title condition="java">How to specify a title for a domain
+ entity</title>
<para>A title is used to identify an object to the user in the user
interface. For example, a <classname>Customer</classname>'s title
@@ -361,8 +418,8 @@ public class Department {
</sect1>
<sect1 id="sec.HowToSpecifyTheIconForAnObjectsClass">
- <title condition=".net">How to specify the icon for an object's
- class</title>
+ <title condition=".net">How to specify the icon for a domain
+ entity</title>
<para condition="1.5">By default, the framework will look for an image
in the <filename class="directory" moreinfo="none">images</filename>
@@ -407,14 +464,12 @@ public class Department {
</sect1>
<sect1 id="sec.MemberOrderForProperties">
- <title>How to specify the order in which properties are
+ <title>How to specify the order in which properties or collections are
displayed</title>
<para>The <literal moreinfo="none">@MemberOrder</literal> annotation
provides a hint to the viewer as to the order in which the properties
- (and also collections, see <xref
- linkend="sec.MemberOrderForCollections" />) should appear in the
- <acronym>GUI</acronym>.</para>
+ and collections should appear in the <acronym>GUI</acronym>.</para>
<para>For example:</para>
@@ -426,27 +481,8 @@ public class Department {
@MemberOrder("2")
public String getLastName() { ... }
...
-}</programlisting>
- </sect1>
-
- <sect1 id="sec.MemberOrderForCollections">
- <title>How to specify the order in which collections are
- displayed</title>
-
- <para>The <literal moreinfo="none">@MemberOrder</literal> annotation
- provides a hint to the viewer as to the order in which the collections
- (and also properties, see <xref
- linkend="sec.MemberOrderForProperties" />) should appear in the
- <acronym>GUI</acronym>.</para>
-
- <para>For example:</para>
- <programlisting format="linespecific">public class Customer {
@MemberOrder("3")
- public Collection<Order> getRecentOrders() { ... }
- ...
-
- @MemberOrder("4")
public Collection<Order> getOrders() { ... }
...
}</programlisting>
@@ -471,6 +507,12 @@ public class Department {
public void blackList() { ... }
...
}</programlisting>
+
+ <para></para>
+
+ <para>***</para>
+
+ <para></para>
</sect1>
<sect1>
@@ -488,8 +530,8 @@ public class Department {
<title>How to make an action parameter optional</title>
<para>By default, the framework assumes that when an action method is
- to be invoked, all the parameters are mandatory. You may over-ride
- this behaviour by marking up one or more of the paramaters with the
+ to be invoked, all the parameters are mandatory. You may override this
+ behaviour by marking up one or more of the paramaters with the
<literal moreinfo="none">@Optional</literal> annotation.</para>
</sect1>
@@ -582,169 +624,7 @@ public class Department {
}</programlisting>
</sect1>
- <sect1>
- <title condition="j#">How to specify a name and/or description for an
- object</title>
-
- <para>By default, the name (or type) of an object, as displayed to the
- user will be the class name. However, if an <literal
- moreinfo="none">@Named</literal> annotation is included, then this
- will override the default name. This might be used to include
- punctuation or other characters that may not be used within a class
- name, or when - for whatever reason - the name of the class includes
- technical artifacts (for example project-defined prefixes or
- suffices). It is also useful if the required name cannot be used
- because it is a keyword in the language.</para>
-
- <para>By default the framework will create a plural version of the
- object name by adding an 's' to singular name, or a 'ies' to names
- ending 'y'. For irregular nouns or other special case, the
- <code>@Plural</code> annotation may be used to specify the plural form
- of the name explicitly.t</para>
-
- <para>The programmer may optionally also provide a <literal
- moreinfo="none">@DescribedAs</literal> annotations, containing a brief
- description of the object's purpose, from a user perspective. The
- framework will make this available to the user in a form appropriate
- to the user interface style - for example as a tooltip.</para>
-
- <para>For example:</para>
-
- <programlisting>@Named("Customer")
-@Plural("Customers")
-@DescribedAs("Individuals or organizations that have either "+
- "purchased from us in the past or "+
- "are likely to in the future")
-public class CustomerImpl implements ICustomer {
- ...
-}</programlisting>
-
- <note>
- <para>There is an entirely separate mechanism for dealing with
- Internationalisation, details of which can be found in the core
- documentation.</para>
- </note>
- </sect1>
-
- <sect1>
- <title>How to specify a name and/or description for a property</title>
-
- <sect2>
- <title>Specifying the name for a property</title>
-
- <para>By default the framework will use the property name itself to
- label the property on the user interface. If you wish to override
- this, use the <literal moreinfo="none">@Named </literal>annotation
- on the property.</para>
-
- <para>For example:</para>
-
- <programlisting format="linespecific">public class Customer() {
- @Named("Given Name")
- public String getFirstName() { ... }
- ...
-}</programlisting>
- </sect2>
-
- <sect2>
- <title>Specifying a description for a property</title>
-
- <para>An additional description can be provided on a property using
- the <literal moreinfo="none">@DescribedAs</literal> annotation. The
- framework will take responsibility to make this description
- available to the user, for example in the form of a tooltip.</para>
-
- <para>For example:</para>
-
- <programlisting format="linespecific">public class Customer() {
- @DescribedAs("The customer's given name")
- public String getFirstName() { ... }
- ...
-}</programlisting>
- </sect2>
- </sect1>
-
- <sect1>
- <title>How to specify a name and/or description for a
- collection</title>
-
- <sect2>
- <title>Specifying the name for a collection</title>
-
- <para>By default the framework will use the collection name itself
- to label the collection on the user interface. If you wish to
- override this, use the <literal moreinfo="none">@Named</literal>
- annotation on the collection.</para>
-
- <para>For example:</para>
-
- <programlisting format="linespecific">public class Customer {
- @Named("Placed Orders")
- public List<Order> getOrders() { ... }
- ...
-}</programlisting>
- </sect2>
-
- <sect2>
- <title>Specifying a description for a collection</title>
-
- <para>An additional description can be provided on a collection
- using the <literal moreinfo="none">@DescribedAs</literal>
- annotation. The framework will take responsibility to make this
- description available to the user, for example in the form of a
- tooltip.</para>
-
- <para>For example:</para>
-
- <programlisting format="linespecific">public class Customer {
- @DescribedAs("Those orders that have been placed (and possibly shipped) " +
- "by this customer given name by which this customer is known")
- public List<Order> getOrders() { ... }
- ...
-}</programlisting>
- </sect2>
- </sect1>
-
- <sect1>
- <title>How to specify names and/or description for an action</title>
-
- <sect2>
- <title>Specifying the name for an action</title>
-
- <para>By default the framework will use the action name itself to
- label the menu item on the user interface. If you wish to override
- this, use the <literal moreinfo="none">@Named</literal> annotation
- on the action.</para>
-
- <para>For example:</para>
-
- <programlisting format="linespecific">public class Customer {
- @Named("Place Order")
- public void createOrder() { ... }
- ...
-}</programlisting>
- </sect2>
-
- <sect2>
- <title>Specifying a description for a collection</title>
-
- <para>An additional description can be provided on an action using
- the <literal moreinfo="none">@DescribedAs</literal> annotation. The
- framework will take responsibility to make this description
- available to the user, for example in the form of a tooltip.</para>
-
- <para>For example:</para>
-
- <programlisting format="linespecific">public class Customer {
- @DescribedAs("Places an order, causing a shipping note "+
- "to be generated and invoice to be dispatched")
- public void createOrder() { ... }
- ...
-}</programlisting>
- </sect2>
- </sect1>
-
- <sect1>
+ <sect1 id="sec.ActionParameterNames">
<title>How to specify names and/or descriptions for an action
parameter</title>
@@ -781,111 +661,28 @@ public class CustomerImpl implements ICustomer {
</sect1>
<sect1>
- <title condition="vb">How to specify that an object should not be
- persisted</title>
-
- <para>Non-persisted objects are intended to be used as view models;
- they aggregate some state with respect to a certain process. This may
- be read-only (eg a projection of certain informaiton) or read-write
- (eg a wizard-like process object). Either way, the viewer is expected
- to interpret this by not providing any sort of automatic "save" menu
- item if such an object is returned to the
- <acronym>GUI</acronym>.</para>
-
- <para>Non-persisted objects that are read-only are typically also
- marked as immutable (see <xref linkend="sec.Immutable" />).</para>
-
- <para>To indicate that an object cannot be persisted, use the <literal
- moreinfo="none">@NotPersistable</literal> annotation.</para>
- </sect1>
-
- <sect1 id="sec.Immutable">
- <title id="sec.entity.immutable">How to specify that an object should
- never be modified by the user</title>
-
- <para>Some objects have state which should not be modifiable; for
- example those representing reference data. The viewer is expected to
- interpret this by which suppressing any sort of "edit" button.</para>
+ <title>How to inject services into a domain entity or other
+ service</title>
- <para>To indicate that an object cannot be modified, use the <literal
- moreinfo="none">@Immutable</literal> annotation.</para>
+ <para>All that is required to inject a service into an entity (or
+ indeed into another service) is to provide an appropriately typed
+ setter. The name of the method does not matter, only that it is
+ prefixed "set", is public, and has a single parameter of the correct
+ type.</para>
<para>For example:</para>
- <programlisting>@Immutable
-public class ChasingLetter implements PaymentReclaimStrategy {
+ <programlisting>public class Customer {
+ private OrderRepository orderRepository;
+ public void setOrderRepository(OrderRepository orderRepository) {
+ this.orderRepository = orderRepository;
+ }
...
}</programlisting>
- <para>See also <xref linkend="sec.Entity.Disabled" />.</para>
- </sect1>
-
- <sect1 id="sec.Entity.Disabled">
- <title>How to control when an object can be modified</title>
-
- <para>Some objects have state which should not be modifiable only
- under certain conditions; for example an invoice can not be modified
- after it has been paid. The viewer is expected to interpret this by
- suppressing any sort of "edit" button.</para>
-
- <para>To indicate that an object cannot be modified, use the <literal
- moreinfo="none">String disabled(Type type)</literal> method.</para>
-
- <para>For example:</para>
-
- <programlisting>public class FeeInvoice implements Invoice {
- public String disabled(Type type){
- ...
- }
-}</programlisting>
-
- <para>The Type is from
- <classname>org.apache.isis.applib.Identifier</classname>:</para>
-
- <programlisting> /**
- * What type of feature this identifies.
- */
- public static enum Type {
- CLASS, PROPERTY_OR_COLLECTION, ACTION
- }</programlisting>
-
- <para>and provides fine grain control over disabling actions and
- properties.</para>
-
- <para>The return String is null if the the object (action or property)
- is not disabled, or the reason why it is disabled, similar to <xref
- linkend="sec.DisabledProperty" />.</para>
-
- <para>See also <xref linkend="sec.Entity.Disabled" />.</para>
- </sect1>
-
- <sect1 id="sec.Bounded">
- <title>How to specify that a class of objects has a limited number of
- instances</title>
-
- <para>Sometimes an entity may only have a relatively small number of
- instances, for example the types of credit cards accepted (Visa,
- Mastercard, Amex). Viewers will typically expected to render the
- complete set of instances as a drop down list whenever the object type
- is used (ie as a property or action parameter).</para>
-
- <para>To indicate that a class has a limited number of instances, use
- the <classname>@Bounded</classname> annotation. Note that there is an
- implied expectation is that the list of objects is small, and
- relatively cheap to obtain from the object store.</para>
-
- <para>An alternative way to specify a selection of objects is using
- the <classname>choicesXxx()</classname> supporting methods.</para>
-
- <para>For example:</para>
-
- <programlisting>@Bounded
-public class PaymentMethod {
- ...
-}</programlisting>
-
- <para>Alternatively, you might want to use a (Java) <code>enum</code>,
- because these are implicitly bounded.</para>
+ <para>Note that we consider
+ <classname>DomainObjectContainer</classname> to be a service too;
+ hence it can be injected in exactly the same manner.</para>
</sect1>
<sect1 id="sec.HowToCreateAnObject">
@@ -1048,18 +845,26 @@ persist(newCust);</programlisting>
</chapter>
<chapter id="chp.Properties">
- <title>Domain Entity Properties</title>
+ <title>How to add business rules</title>
<abstract>
- <para>How-to's relating to an domain entity's properties.</para>
+ <para>How-to add business rules to domain entities and services,
+ controlling whether a domain entity or service's class members are
+ visible, if they are enabled, and to validate arguments. </para>
</abstract>
- <para>The following conventions are concerned with specifying the
- properties of a domain entity, and the means by which a user can
- interact with those properties.</para>
+ <para>Business rules can be added to domain objects in a number of ways.
+ As well as the business logic encapsulated by domain object actions, the
+ framework also supports a number of conventions that allow a domain
+ entity or service's members to be made visible or hidden, to be enabled
+ or disabled (greyed out), and to validate arguments when invoking an
+ action, setting a new value for a property, or if adding a new element
+ to a collection.</para>
+
+ <para>Or, in other words: "see it, use it, do it".</para>
<sect1 id="sec.HiddenProperty">
- <title>How to hide a property from the user</title>
+ <title>How to hide a property</title>
<para>The mechanism for hiding a property is broadly the same as for
hiding a collection (see <xref linkend="sec.HiddenCollection" />) or
@@ -1283,18 +1088,9 @@ persist(newCust);</programlisting>
<programlisting><literal moreinfo="none">public boolean hideActionName([ValueOrEntityType param] ...)</literal> </programlisting>
- <para>where the parameter types should match the action itself
- (allowing for overloading)</para>
-
- <para>or</para>
-
- <programlisting><literal moreinfo="none">public boolean hideActionName()</literal> </programlisting>
-
- <para>which applies to (all overloaded versions of) the
- action.</para>
-
- <para>in both cases, returning a value of <code>true</code>
- indicates that the action should be hidden.</para>
+ <para>where the parameter types should match the action itself.
+ Returning a value of <code>true</code> indicates that the action
+ should be hidden.</para>
<para>For example:</para>
@@ -1318,7 +1114,8 @@ persist(newCust);</programlisting>
</sect1>
<sect1 id="sec.Entity.Hidden">
- <title>How to control when an object is visible</title>
+ <title>How to specify that none of an object's members is
+ visible</title>
<para>To when an object is visible, provide a hidden() method:</para>
@@ -1616,6 +1413,67 @@ persist(newCust);</programlisting>
</sect2>
</sect1>
+ <sect1 id="sec.Entity.Disabled">
+ <title>How to specify that none of an object's members can be
+ modified/invoked</title>
+
+ <para>Some objects have state which should not be modifiable only
+ under certain conditions; for example an invoice can not be modified
+ after it has been paid. The viewer is expected to interpret this by
+ suppressing any sort of "edit" button.</para>
+
+ <para>To indicate that an object cannot be modified, use the <literal
+ moreinfo="none">String disabled(Type type)</literal> method.</para>
+
+ <para>For example:</para>
+
+ <programlisting>public class FeeInvoice implements Invoice {
+ public String disabled(Type type){
+ ...
+ }
+}</programlisting>
+
+ <para>The <code>Type</code> is from
+ <classname>org.apache.isis.applib.Identifier</classname>:</para>
+
+ <programlisting> /**
+ * What type of feature this identifies.
+ */
+ public static enum Type {
+ CLASS, PROPERTY_OR_COLLECTION, ACTION
+ }</programlisting>
+
+ <para>and provides fine grain control over disabling actions and
+ properties.</para>
+
+ <para>The return String is null if the the object (action or property)
+ is not disabled, or the reason why it is disabled, similar to <xref
+ linkend="sec.DisabledProperty" />.</para>
+
+ <para>See also <xref linkend="sec.Entity.Disabled" />.</para>
+ </sect1>
+
+ <sect1 id="sec.Immutable">
+ <title id="sec.entity.immutable">How to specify that an object is
+ immutable (that none of its members can ever be modified)</title>
+
+ <para>Some objects have state which should not be modifiable; for
+ example those representing reference data. The viewer is expected to
+ interpret this by which suppressing any sort of "edit" button.</para>
+
+ <para>To indicate that an object cannot be modified, use the <literal
+ moreinfo="none">@Immutable</literal> annotation.</para>
+
+ <para>For example:</para>
+
+ <programlisting>@Immutable
+public class ChasingLetter implements PaymentReclaimStrategy {
+ ...
+}</programlisting>
+
+ <para>See also <xref linkend="sec.Entity.Disabled" />.</para>
+ </sect1>
+
<sect1>
<title>How to validate user input for a property</title>
@@ -1776,6 +1634,91 @@ persist(newCust);</programlisting>
</sect2>
</sect1>
+ <sect1 id="sec.MustSpecify">
+ <title>How to validate declaratively using @MustSatisfy</title>
+
+ <para>The <literal>@MustSatisfy</literal> annotation is an alternative
+ to using imperative validation, allowing validation rules to be
+ captured in an (implementation of a)
+ <classname>org.apache.isis.applib.spec.Specification</classname>.</para>
+
+ <para>For example:</para>
+
+ <programlisting format="linespecific">public class DomainObjectWithMustSatisfyAnnotations extends AbstractDomainObject {
+
+ private String lastName;
+ @MustSatisfy(SpecificationRequiresFirstLetterToBeUpperCase.class)
+ public String getLastName() {
+ resolve(lastName);
+ return lastName;
+ }
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ objectChanged();
+ }
+
+ public void changeLastName(
+ @MustSatisfy(SpecificationRequiresFirstLetterToBeUpperCase.class)
+ String lastName
+ ) {
+ setLastName(lastName);
+ }
+
+}</programlisting>
+
+ <para>To help you write your own
+ <classname>Specification</classname>s, there are some adapter classes
+ in <package>org.apache.isis.applib.specs</package>:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para><classname>AbstractSpecification</classname>, which
+ implements <classname>Specification</classname> and takes
+ inspiration from the <ulink
+ url="http://code.google.com/p/hamcrest/">Hamcrest</ulink>
+ library's <classname>TypeSafeMatcher</classname> class</para>
+ </listitem>
+
+ <listitem>
+ <para><classname>SpecificationAnd</classname>, which allows a set
+ of <classname>Specification</classname>s to be grouped together
+ and require that <emphasis>all</emphasis> of them are
+ satisfied</para>
+ </listitem>
+
+ <listitem>
+ <para><classname>SpecificationOr</classname>, which allows a set
+ of <classname>Specification</classname>s to be grouped together
+ and require that <emphasis>at least one</emphasis> of them is
+ satisfied</para>
+ </listitem>
+
+ <listitem>
+ <para><classname>SpecificationNot</classname>, which requires that
+ the provided <classname>Specification</classname> is
+ <emphasis>not</emphasis> satisfied</para>
+ </listitem>
+ </itemizedlist>
+ </sect1>
+ </chapter>
+
+ <chapter>
+ <title>How to provide drop-downs and default values</title>
+
+ <abstract>
+ <para>How-to make actions easier to use from an end-user perspective,
+ by providing sets of choices and defaults.</para>
+ </abstract>
+
+ <para>Invoking actions or setting properties requires that the user
+ specify a valid value; of the correct type, and that passes any
+ validation rules that may have been defined. To make things are easier
+ for the user, you can provide lists of choices; viewers typically render
+ these values in a drop-down list box.</para>
+
+ <para>In a similar vein, there may be a default value for an action
+ parameter; this can also be specified.</para>
+
<sect1>
<title>How to specify a set of choices for a property</title>
@@ -1813,103 +1756,255 @@ persist(newCust);</programlisting>
}</programlisting>
</sect1>
- <sect1 id="sec.ModifyAndClear">
- <title>How to trigger other behaviour when a property is
- changed</title>
+ <sect1>
+ <title>How to specify a set of choices for an action parameter</title>
- <para>If you want to invoke functionality whenever a property is
- changed by the user, then you should create a supporting <literal
- moreinfo="none">modifyXxx()</literal> method and include the
- functionality within that. The syntax is:</para>
+ <para>The programmer may provide a set of choices for the value of any
+ or all of the parameters of an action. These will be made available to
+ the user - for example as a drop-down list.</para>
- <programlisting>public void modifyPropertyName(PropertyType param)</programlisting>
+ <para>If the type of the parameter is annotated with <literal
+ moreinfo="none">@Bounded</literal>, then it is not necessary to
+ specify the choices for that parameter, as the user will automatically
+ be offered the full set to choose from.</para>
- <para>Why not just put this functionality in the setter? Well, the
- setter is used by the object store to recreate the state of an already
- persisted object. Putting additional behaviour in the setter would
- cause it to be triggered incorrectly.</para>
+ <para>If this isn't the case, then - as for defaults - there are two
+ different ways to specify parameters; either per parameter, or for all
+ parameters.</para>
- <para>For example:</para>
+ <sect2>
+ <title>Per parameter syntax (preferred)</title>
- <programlisting format="linespecific">public class Order() {
- public Integer getAmount() { ... }
- public void setAmount(Integer amount) { ... }
- public void modifyAmount(Integer amount) {
- setAmount(amount);
- addToTotal(amount);
- }
- ...
-}</programlisting>
+ <para>The per-parameter form is simpler and generally
+ preferred.</para>
- <para>Here the <literal moreinfo="none">modifyAmount()</literal>
- method also calls the <literal moreinfo="none">addToTotal()</literal>
- call as well as the <literal moreinfo="none">setAmount()</literal>
- method. We don't want this <methodname>addToCall()</methodname> method
- to be called when pulling the object back from the object store, so we
- put it into the modify, not the setter.</para>
+ <para>The syntax is:</para>
- <para>You may optionally also specify a <code>clearXxx()</code> which
- works the same way as modify <literal moreinfo="none">modify</literal>
- <literal moreinfo="none">Xxx()</literal> but is called when the
- property is cleared by the user (i.e. the current value replaced by
- nothing). The syntax is:</para>
+ <programlisting>public List<ParameterType> choicesNActionName()</programlisting>
- <programlisting>public void clearPropertyName()</programlisting>
+ <para>where N indicates the 0-based parameter number.</para>
- <para>To extend the above example:</para>
+ <para>For example:</para>
- <programlisting format="linespecific">public class Order() {
- public Integer getAmount() { ... }
- public void setAmount(Integer amount) { ... }
- public void modifyAmount(Integer amount) { ... }
- public void clearAmount() {
- removeFromTotal(this.amount);
- setAmount(null);
+ <programlisting format="linespecific">public class Order {
+ public Order placeOrder(
+ Product product,
+ @Named("Quantity")
+ int quantity) {
+ ...
+ }
+ public List<Product> choices0PlaceOrder() {
+ return lastFiveProductsOrderedBy(this.getCustomer());
+ }
+ public List<Integer> choices1PlaceOrder() {
+ return Arrays.asList(1,2,3,4,5);
+ }
+ ....
+}</programlisting>
+ </sect2>
+
+ <sect2>
+ <title>All parameters syntax</title>
+
+ <para>The alternative mechanism is to supply all the parameter
+ choices in one go:</para>
+
+ <para><programlisting>public Object[] choices<ActionName>([<parameter type> param]...)</programlisting></para>
+
+ <para>returning an array, which must have one element per parameter
+ in the method signature. Each element of the array should itself
+ either be an array or a list, containing the set of values
+ representing the choices for that parameter, or <literal
+ moreinfo="none">null</literal> if there are no choices to be
+ specified for that parameter.</para>
+
+ <para>For example:</para>
+
+ <programlisting format="linespecific">public class Order {
+ public Order placeOrder(
+ Product product,
+ @Named("Quantity")
+ int quantity) {
+ ...
+ }
+ public Object[] choicesPlaceOrder(
+ Product product,
+ int quantity) {
+ return new Object[] {
+ lastFiveProductsOrderedBy(this.getCustomer()).toArray(),
+ Arrays.asList(1,2,3,4,5)
+ };
}
...
}</programlisting>
+ </sect2>
+ </sect1>
+
+ <sect1 id="sec.Bounded">
+ <title>How to specify that a class of objects has a limited number of
+ instances</title>
+
+ <para>Sometimes an entity may only have a relatively small number of
+ instances, for example the types of credit cards accepted (Visa,
+ Mastercard, Amex). Viewers will typically expected to render the
+ complete set of instances as a drop down list whenever the object type
+ is used (ie as a property or action parameter).</para>
+
+ <para>To indicate that a class has a limited number of instances, use
+ the <classname>@Bounded</classname> annotation. Note that there is an
+ implied expectation is that the list of objects is small, and
+ relatively cheap to obtain from the object store.</para>
+
+ <para>An alternative way to specify a selection of objects is using
+ the <classname>choicesXxx()</classname> supporting methods.</para>
+
+ <para>For example:</para>
+
+ <programlisting>@Bounded
+public class PaymentMethod {
+ ...
+}</programlisting>
+
+ <para>Alternatively, you might want to use a (Java) <code>enum</code>,
+ because these are implicitly bounded.</para>
</sect1>
<sect1>
- <title>How to setup a bidirectional relationship</title>
+ <title>How to find an entity (for an action parameter or property)
+ using auto-complete</title>
- <para>The <methodname>modifyXxx()</methodname> and
- <methodname>clearXxx()</methodname> methods (see <xref
- linkend="sec.ModifyAndClear" />) can be used to setup bidirectional
- relationships. This is typically done with 1:m relationships, eg
- between <classname>Order</classname> and
- <classname>OrderLine</classname>, or <classname>Department</classname>
- and <classname>Employee</classname>.</para>
+ <para>Some viewers (eg the Wicket viewer) allow an entity to be
+ specified (as an argument for an action parameter, or as the new value
+ of a property) by enabling the user to type its title. The framework
+ then searches for a matching instance, presenting them in a drop-down
+ list.</para>
+
+ <para>This is accomplished using two annotations. The
+ <classname>@AutoComplete</classname> annotation is used on the entity
+ type itself:</para>
+
+ <programlisting>@AutoComplete(repository=Customers.class)
+public class Customer {
+ ...
+}</programlisting>
+
+ <para>The <code>repository</code> attribute indicates the class of the
+ domain service that has an autoComplete() method. This is required to
+ accept a single <classname>String</classname> and return a
+ <classname>List</classname> of candidates:</para>
+
+ <programlisting>public class Customers {
+ ...
+ @Hidden
+ public List<Property> autoComplete(String searchPhrase) {
+ return allMatches(new QueryDefault<Customer>("customers_findByName", "name", searchPhrase));
+ }
+}</programlisting>
+
+ <para>If required, a different action name than "autoComplete" can be
+ specified.</para>
+ </sect1>
+
+ <sect1>
+ <title>How to specify default values for an action parameter</title>
+
+ <para>When an action is about to be invoked, then default values can
+ be provided for any or all of its parameters.</para>
+
+ <para>There are two different ways to specify parameters; either per
+ parameter, or for all parameters.</para>
+
+ <sect2>
+ <title>Per-parameter syntax (preferred)</title>
+
+ <para>The per-parameter form is simpler and generally to be
+ preferred.</para>
+
+ <para>The syntax is:</para>
+
+ <programlisting>public ParameterType defaultNActionName()</programlisting>
- <para>For further discussion and a complete example, see <xref
- linkend="sec.MutualRegistrationPattern" />.</para>
+ <para>where N indicates the 0-based parameter number. For
+ example:</para>
+
+ <programlisting format="linespecific">public class Customer {
+ public Order placeOrder(
+ Product product,
+ @Named("Quantity")
+ int quantity) {
+ ...
+ }
+ public Product default0PlaceOrder() {
+ return productMostRecentlyOrderedBy(this.getCustomer());
+ }
+ public int default1PlaceOrder() {
+ return 1;
+ }
+}</programlisting>
+ </sect2>
+
+ <sect2>
+ <title>All parameters syntax</title>
+
+ <para>The syntax for specifying all the parameter default values in
+ one go is:</para>
+
+ <para><programlisting>public Object[] defaultActionName([ValueOrEntityType param]...)</programlisting></para>
+
+ <para>returning an array which must have one element per parameter
+ in the action method signature of corresponding default
+ values.</para>
+
+ <para>For example:</para>
+
+ <programlisting format="linespecific">public class Customer {
+ public Order placeOrder(
+ Product product,
+ @Named("Quantity")
+ int quantity) {
+ ...
+ }
+ public Object[] defaultPlaceOrder(
+ Product product,
+ int quantity) {
+ return new Object[] {
+ productMostRecentlyOrderedBy(this.getCustomer()),
+ 1
+ };
+ }
+ ...
+}</programlisting>
+ </sect2>
</sect1>
</chapter>
- <chapter id="chp.Collections">
- <title>Domain Entity Collections</title>
+ <chapter>
+ <title>How to derive properties and collections, and other
+ side-effects</title>
<abstract>
- <para>How-to's relating to a domain entity's collections.</para>
+ <para>How-to derive properties and collections from persisting state,
+ and provide other side-effects.</para>
</abstract>
- <para>A collection is a list of references to several entity that have a
- common type. The following conventions are concerned with specifying the
- collections of an object, and the means by which a user can interact
- with those collections.</para>
+ <para>The Isis viewers will automatically render the state of properties
+ and collections, but the values of such need not be persisted; they can
+ be derived from other information available to the object. </para>
+
+ <para>For collections</para>
<sect1 id="sec.DerivedProperty">
<title>How to make a derived property</title>
<sect2>
- <title>Lazily derived</title>
+ <title>Read-only</title>
+
+ <para>Most derived properties are read-only, their value being
+ derived from other information available to the object.</para>
<para>Omitting the mutator (<code>setXxx()</code>) method for a
property indicates both that the field is derived, and is not be
- persisted. This is the standard approach to derive a property from
- other information available to the object. It also happens to be
- compatible with Java Persistence Api (<acronym>JPA</acronym>)
- semantics.</para>
+ persisted.</para>
<para>For example:</para>
@@ -1924,9 +2019,14 @@ persist(newCust);</programlisting>
}
...
}</programlisting>
+ </sect2>
+
+ <sect2>
+ <title>Read-write</title>
- <para>Although there is no setter, such properties can nevertheless
- be modified if a modifyXxx supporting method is provided:</para>
+ <para>A derived property can be made updateable (in that it takes
+ the provided value and does something sensible with it) by providing
+ a <code>modifyXxx()</code> supporting method:</para>
<programlisting format="linespecific">public class Employee {
public Department getDepartment() { ... }
@@ -1944,48 +2044,37 @@ persist(newCust);</programlisting>
...
}</programlisting>
- <para>Note how the implementation of such a modifyXxx() method
- typically modifies the original source of the information (the
- <classname>Department</classname> object).</para>
- </sect2>
-
- <sect2>
- <title>Eagerly derived</title>
-
- <para>An alternative to omitting the mutator is to mark a property
- is to keep the mutator, but to annotate the property with
- <classname>@NotPersisted</classname>.</para>
-
- <para>Because the data is non-persisted, some other mechanism must
- be used to initialize the property. Typically this will be a
- lifecycle method (see <xref linkend="sec.LifecycleMethods" />),
- which eagerly derive the value of the property.</para>
+ <para>Note how the implementation of such a <code>modifyXxx()</code>
+ method typically modifies the original source of the information
+ (the <classname>Department</classname> object).</para>
- <para>For example:</para>
+ <para>Alternatively, if you prefer to have a setter, then you can
+ use Isis' <classname>@NotPersisted</classname> attribute.</para>
<programlisting format="linespecific">public class Employee {
- public void loaded() {
- if (getDepartment() != null) {
- setManager(getDepartment().getManager());
- }
- }
-
public Department getDepartment() { ... }
...
@NotPersisted
public Employee getManager() { ... }
- private void setManager(Employee manager) { ... }
+ public void setManager(Employee manager) {
+ if (getDepartment() == null) { return; }
+ getDepartment().modifyManager(manager);
+ }
...
}</programlisting>
- <para>Eagerly derived properties can be made modifiable either
- changing the setter to have public visibility, or introducing a
- modifyXxx() method.</para>
+ <note>
+ <para>If you use this approach, then for some object stores you
+ may also need to annotate the property to exclude it. For example
+ the JDO/DataNucleus object store requires the property being
+ annotated with
+ <classname>@javax.jdo.annotations.NotPersistent</classname>.</para>
+ </note>
</sect2>
</sect1>
- <sect1>
+ <sect1 id="sec.DerivedCollection">
<title>How to make a derived collection</title>
<para>Collections can be derived by omitting the mutator (the same way
@@ -2013,9 +2102,9 @@ persist(newCust);</programlisting>
}</programlisting>
<para>Derived collections are not persisted, though may be modified if
- there is an addToXxx() or removeFromXxx() supporting method. As for
- derived properties, the implementations of these mutators change the
- underlying data. For example:</para>
+ there is an <code>addToXxx()</code> or <code>removeFromXxx()</code>
+ supporting method. As for derived properties, the implementations of
+ these mutators change the underlying data. For example:</para>
<programlisting format="linespecific">public class Department {
...
@@ -2029,46 +2118,124 @@ persist(newCust);</programlisting>
}</programlisting>
</sect1>
- <sect1 id="sec.AddToRemoveFrom">
- <title>How to trigger other behaviour when an object is added or
- removed</title>
-
- <para>A collection may have a corresponding <literal
- moreinfo="none">addToXxx()</literal> and/or <literal
- moreinfo="none">removeFromXxx()</literal> method. If present, and
- direct manipulation of the contents of the connection has not been
- disabled (see <xref linkend="sec.DisabledCollection" />), then they
- will be called (instead of adding/removing an object directly to the
- collection returned by the accessor).</para>
+ <sect1>
+ <title>How to inline the results of a query-only repository
+ action</title>
+
+ <para>While derived properties (<xref
+ linkend="sec.DerivedProperty" />) and derived collections (<xref
+ linkend="sec.DerivedCollection" />) typically "walk the graph" to
+ associated objects, there is nothing to prevent the returned value
+ being the result of invoking a repository (domain service)
+ action.</para>
- <para>The reason for this behaviour is to allow other behaviour to be
- triggered when the contents of the collection is altered. That is, it
- is directly equivalent to the supporting <literal
- moreinfo="none">modifyXxx()</literal> and <literal
- moreinfo="none">clearXxx()</literal> methods for properties (see <xref
- linkend="sec.ModifyAndClear" />).</para>
+ <para>For example:</para>
- <para>The syntax is:</para>
+ <programlisting>public class Customer {
+ ...
+ public List<Order> getMostRecentOrders() {
+ return orderRepo.findMostRecentOrders(this, 5);
+ }
+}</programlisting>
+ </sect1>
- <para><programlisting>public void addTo<CollectionName>(EntityType param)</programlisting></para>
+ <sect1 id="sec.ModifyAndClear">
+ <title>How to trigger other behaviour when a property is
+ changed</title>
- <para>and</para>
+ <para>If you want to invoke functionality whenever a property is
+ changed by the user, then you should create a supporting <literal
+ moreinfo="none">modifyXxx()</literal> method and include the
+ functionality within that. The syntax is:</para>
- <programlisting>public void removeFromCollectionName(EntityType param)</programlisting>
+ <programlisting>public void modifyPropertyName(PropertyType param)</programlisting>
- <para>where <literal moreinfo="none">EntityType</literal> is the same
- type as the generic collection type.</para>
+ <para>Why not just put this functionality in the setter? Well, the
+ setter is used by the object store to recreate the state of an already
+ persisted object. Putting additional behaviour in the setter would
+ cause it to be triggered incorrectly.</para>
<para>For example:</para>
- <programlisting format="linespecific">public class Employee { ... }
-
-public class Department {
- private List<Employee> employees = new ArrayList<Employee>();
- public List <Employee> getEmployees() {
- return employees;
+ <programlisting format="linespecific">public class Order() {
+ public Integer getAmount() { ... }
+ public void setAmount(Integer amount) { ... }
+ public void modifyAmount(Integer amount) {
+ setAmount(amount);
+ addToTotal(amount);
}
- private void setEmployees(List<Employee> employees) {
+ ...
+}</programlisting>
+
+ <para>Here the <literal moreinfo="none">modifyAmount()</literal>
+ method also calls the <literal moreinfo="none">addToTotal()</literal>
+ call as well as the <literal moreinfo="none">setAmount()</literal>
+ method. We don't want this <methodname>addToCall()</methodname> method
+ to be called when pulling the object back from the object store, so we
+ put it into the modify, not the setter.</para>
+
+ <para>You may optionally also specify a <code>clearXxx()</code> which
+ works the same way as modify <literal moreinfo="none">modify</literal>
+ <literal moreinfo="none">Xxx()</literal> but is called when the
+ property is cleared by the user (i.e. the current value replaced by
+ nothing). The syntax is:</para>
+
+ <programlisting>public void clearPropertyName()</programlisting>
+
+ <para>To extend the above example:</para>
+
+ <programlisting format="linespecific">public class Order() {
+ public Integer getAmount() { ... }
+ public void setAmount(Integer amount) { ... }
+ public void modifyAmount(Integer amount) { ... }
+ public void clearAmount() {
+ removeFromTotal(this.amount);
+ setAmount(null);
+ }
+ ...
+}</programlisting>
+ </sect1>
+
+ <sect1 id="sec.AddToRemoveFrom">
+ <title>How to trigger other behaviour when an object is added or
+ removed</title>
+
+ <para>A collection may have a corresponding <literal
+ moreinfo="none">addToXxx()</literal> and/or <literal
+ moreinfo="none">removeFromXxx()</literal> method. If present, and
+ direct manipulation of the contents of the connection has not been
+ disabled (see <xref linkend="sec.DisabledCollection" />), then they
+ will be called (instead of adding/removing an object directly to the
+ collection returned by the accessor).</para>
+
+ <para>The reason for this behaviour is to allow other behaviour to be
+ triggered when the contents of the collection is altered. That is, it
+ is directly equivalent to the supporting <literal
+ moreinfo="none">modifyXxx()</literal> and <literal
+ moreinfo="none">clearXxx()</literal> methods for properties (see <xref
+ linkend="sec.ModifyAndClear" />).</para>
+
+ <para>The syntax is:</para>
+
+ <para><programlisting>public void addTo<CollectionName>(EntityType param)</programlisting></para>
+
+ <para>and</para>
+
+ <programlisting>public void removeFromCollectionName(EntityType param)</programlisting>
+
+ <para>where <literal moreinfo="none">EntityType</literal> is the same
+ type as the generic collection type.</para>
+
+ <para>For example:</para>
+
+ <programlisting format="linespecific">public class Employee { ... }
+
+public class Department {
+ private List<Employee> employees = new ArrayList<Employee>();
+ public List <Employee> getEmployees() {
+ return employees;
+ }
+ private void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public void addToEmployees(Employee employee) {
@@ -2089,7 +2256,15 @@ public class Department {
</sect1>
<sect1 id="sec.MutualRegistrationPattern">
- <title>How to maintain bidirectional relationships</title>
+ <title>How to set up and maintain bidirectional relationships</title>
+
+ <para>The <methodname>modifyXxx()</methodname> and
+ <methodname>clearXxx()</methodname> methods (see <xref
+ linkend="sec.ModifyAndClear" />) can be used to setup bidirectional
+ relationships. This is typically done with 1:m relationships, eg
+ between <classname>Order</classname> and
+ <classname>OrderLine</classname>, or <classname>Department</classname>
+ and <classname>Employee</classname>.</para>
<para>The recommended way of maintaining a bidirectional relationship
is to use the 'mutual registration pattern', a write-up of which can
@@ -2143,303 +2318,240 @@ public class Department {
}
...
}</programlisting>
+
+ <note>
+ <para>For some object stores this explicit coding may not be
+ necessary; the object store automatically maintains the
+ relationship.</para>
+ </note>
</sect1>
</chapter>
- <chapter id="chp.Actions">
- <title>Domain Entity Actions</title>
+ <chapter>
+ <title>How to provide additional UI hints</title>
<abstract>
- <para>How-to's relating to a domain entity's actions.</para>
+ <para>How to override Isis' defaults for presentation.</para>
</abstract>
- <para>An 'action' is a method that we expect the user to be able to
- invoke on a domain entity via the user interface, though it may also be
- invoked programmatically within the object model. The following
- conventions are used to determine when and how methods are made
- available to the user as actions, and the means by which a user can
- interact with those actions.</para>
+ <para>With the exception of value types for action parameters (see <xref
+ linkend="sec.ActionParameterNames" />), Isis can normally infer a
+ reasonable name for entity/service and its class members. However, these
+ defaults can be overridden if required. One possible case is where the
+ desired name is a reserved word in Java (eg "default", or
+ "package").</para>
+
+ <para>A slightly more advanced use-case is to specify an icon not for an
+ entity's type, but for an entity instance. Typically this reflects that
+ instance's state, eg with an overlay on top of the base icon. For
+ example, this allows the user to distinguish between an
+ <classname>Order</classname> that has been placed vs one that has been
+ shipped.</para>
<sect1>
- <title>How to specify default values for an action parameter</title>
+ <title condition="j#">How to specify a name and/or description for an
+ object</title>
- <para>When an action is about to be invoked, then default values can
- be provided for any or all of its parameters.</para>
+ <para>By default, the name (or type) of an object, as displayed to the
+ user will be the class name. However, if an <literal
+ moreinfo="none">@Named</literal> annotation is included, then this
+ will override the default name. This might be used to include
+ punctuation or other characters that may not be used within a class
+ name, or when - for whatever reason - the name of the class includes
+ technical artifacts (for example project-defined prefixes or
+ suffices). It is also useful if the required name cannot be used
+ because it is a keyword in the language.</para>
- <para>There are two different ways to specify parameters; either per
- parameter, or for all parameters.</para>
+ <para>By default the framework will create a plural version of the
+ object name by adding an 's' to singular name, or a 'ies' to names
+ ending 'y'. For irregular nouns or other special case, the
+ <code>@Plural</code> annotation may be used to specify the plural form
+ of the name explicitly.t</para>
- <sect2>
- <title>Per-parameter syntax (preferred)</title>
+ <para>The programmer may optionally also provide a <literal
+ moreinfo="none">@DescribedAs</literal> annotations, containing a brief
+ description of the object's purpose, from a user perspective. The
+ framework will make this available to the user in a form appropriate
+ to the user interface style - for example as a tooltip.</para>
- <para>The per-parameter form is simpler and generally to be
- preferred.</para>
+ <para>For example:</para>
- <para>The syntax is:</para>
+ <programlisting>@Named("Customer")
+@Plural("Customers")
+@DescribedAs("Individuals or organizations that have either "+
+ "purchased from us in the past or "+
+ "are likely to in the future")
+public class CustomerImpl implements ICustomer {
+ ...
+}</programlisting>
- <programlisting>public ParameterType defaultNActionName()</programlisting>
+ <note>
+ <para>There is an entirely separate mechanism for dealing with
+ Internationalisation, details of which can be found in the core
+ documentation.</para>
+ </note>
+ </sect1>
- <para>where N indicates the 0-based parameter number. For
- example:</para>
+ <sect1>
+ <title>How to specify a name and/or description for a property</title>
- <programlisting format="linespecific">public class Customer {
- public Order placeOrder(
- Product product,
- @Named("Quantity")
- int quantity) {
- ...
- }
- public Product default0PlaceOrder() {
- return productMostRecentlyOrderedBy(this.getCustomer());
- }
- public int default1PlaceOrder() {
- return 1;
- }
+ <sect2>
+ <title>Specifying the name for a property</title>
+
+ <para>By default the framework will use the property name itself to
+ label the property on the user interface. If you wish to override
+ this, use the <literal moreinfo="none">@Named </literal>annotation
+ on the property.</para>
+
+ <para>For example:</para>
+
+ <programlisting format="linespecific">public class Customer() {
+ @Named("Given Name")
+ public String getFirstName() { ... }
+ ...
}</programlisting>
</sect2>
<sect2>
- <title>All parameters syntax</title>
-
- <para>The syntax for specifying all the parameter default values in
- one go is:</para>
-
- <para><programlisting>public Object[] defaultActionName([ValueOrEntityType param]...)</programlisting></para>
+ <title>Specifying a description for a property</title>
- <para>returning an array which must have one element per parameter
- in the action method signature of corresponding default
- values.</para>
+ <para>An additional description can be provided on a property using
+ the <literal moreinfo="none">@DescribedAs</literal> annotation. The
+ framework will take responsibility to make this description
+ available to the user, for example in the form of a tooltip.</para>
<para>For example:</para>
- <programlisting format="linespecific">public class Customer {
- public Order placeOrder(
- Product product,
- @Named("Quantity")
- int quantity) {
- ...
- }
- public Object[] defaultPlaceOrder(
- Product product,
- int quantity) {
- return new Object[] {
- productMostRecentlyOrderedBy(this.getCustomer()),
- 1
- };
- }
+ <programlisting format="linespecific">public class Customer() {
+ @DescribedAs("The customer's given name")
+ public String getFirstName() { ... }
...
}</programlisting>
</sect2>
</sect1>
<sect1>
- <title>How to specify a set of choices for an action parameter</title>
-
- <para>The programmer may provide a set of choices for the value of any
- or all of the parameters of an action. These will be made available to
- the user - for example as a drop-down list.</para>
-
- <para>If the type of the parameter is annotated with <literal
- moreinfo="none">@Bounded</literal>, then it is not necessary to
- specify the choices for that parameter, as the user will automatically
- be offered the full set to choose from.</para>
-
- <para>If this isn't the case, then - as for defaults - there are two
- different ways to specify parameters; either per parameter, or for all
- parameters.</para>
+ <title>How to specify a name and/or description for a
+ collection</title>
<sect2>
- <title>Per parameter syntax (preferred)</title>
-
- <para>The per-parameter form is simpler and generally
- preferred.</para>
-
- <para>The syntax is:</para>
-
- <programlisting>public List<ParameterType> choicesNActionName()</programlisting>
+ <title>Specifying the name for a collection</title>
- <para>where N indicates the 0-based parameter number.</para>
+ <para>By default the framework will use the collection name itself
+ to label the collection on the user interface. If you wish to
+ override this, use the <literal moreinfo="none">@Named</literal>
+ annotation on the collection.</para>
<para>For example:</para>
- <programlisting format="linespecific">public class Order {
- public Order placeOrder(
- Product product,
- @Named("Quantity")
- int quantity) {
- ...
- }
- public List<Product> choices0PlaceOrder() {
- return lastFiveProductsOrderedBy(this.getCustomer());
- }
- public List<Integer> choices1PlaceOrder() {
- return Arrays.asList(1,2,3,4,5);
- }
- ....
+ <programlisting format="linespecific">public class Customer {
+ @Named("Placed Orders")
+ public List<Order> getOrders() { ... }
+ ...
}</programlisting>
</sect2>
<sect2>
- <title>All parameters syntax</title>
-
- <para>The alternative mechanism is to supply all the parameter
- choices in one go:</para>
-
- <para><programlisting>public Object[] choices<ActionName>([<parameter type> param]...)</programlisting></para>
+ <title>Specifying a description for a collection</title>
- <para>returning an array, which must have one element per parameter
- in the method signature. Each element of the array should itself
- either be an array or a list, containing the set of values
- representing the choices for that parameter, or <literal
- moreinfo="none">null</literal> if there are no choices to be
- specified for that parameter.</para>
+ <para>An additional description can be provided on a collection
+ using the <literal moreinfo="none">@DescribedAs</literal>
+ annotation. The framework will take responsibility to make this
+ description available to the user, for example in the form of a
+ tooltip.</para>
<para>For example:</para>
- <programlisting format="linespecific">public class Order {
- public Order placeOrder(
- Product product,
- @Named("Quantity")
- int quantity) {
- ...
- }
- public Object[] choicesPlaceOrder(
- Product product,
- int quantity) {
- return new Object[] {
- lastFiveProductsOrderedBy(this.getCustomer()).toArray(),
- Arrays.asList(1,2,3,4,5)
- };
- }
+ <programlisting format="linespecific">public class Customer {
+ @DescribedAs("Those orders that have been placed (and possibly shipped) " +
+ "by this customer given name by which this customer is known")
+ public List<Order> getOrders() { ... }
...
}</programlisting>
</sect2>
</sect1>
- </chapter>
-
- <chapter>
- <title>Further Business Rule How-Tos</title>
- <abstract>
- <para>Further validation how-to's that apply across all class
- members</para>
- </abstract>
+ <sect1>
+ <title>How to specify names and/or description for an action</title>
- <para>This chapter has some additional recipes/how-tos relating to
- implementing business rules. They apply across all class members.</para>
+ <sect2>
+ <title>Specifying the name for an action</title>
- <sect1 id="sec.MustSpecify">
- <title>@MustSatisfy Specification</title>
+ <para>By default the framework will use the action name itself to
+ label the menu item on the user interface. If you wish to override
+ this, use the <literal moreinfo="none">@Named</literal> annotation
+ on the action.</para>
- <para>The <literal>@MustSatisfy</literal> annotation is an alternative
- to using imperative validation, allowing validation rules to be
- captured in an (implementation of a)
- <classname>org.apache.isis.applib.spec.Specification</classname>.</para>
+ <para>For example:</para>
- <para>For example:</para>
+ <programlisting format="linespecific">public class Customer {
+ @Named("Place Order")
+ public void createOrder() { ... }
+ ...
+}</programlisting>
+ </sect2>
- <programlisting format="linespecific">public class DomainObjectWithMustSatisfyAnnotations extends AbstractDomainObject {
+ <sect2>
+ <title>Specifying a description for a collection</title>
- private String lastName;
- @MustSatisfy(SpecificationRequiresFirstLetterToBeUpperCase.class)
- public String getLastName() {
- resolve(lastName);
- return lastName;
- }
- public void setLastName(String lastName) {
- this.lastName = lastName;
- objectChanged();
- }
+ <para>An additional description can be provided on an action using
+ the <literal moreinfo="none">@DescribedAs</literal> annotation. The
+ framework will take responsibility to make this description
+ available to the user, for example in the form of a tooltip.</para>
- public void changeLastName(
- @MustSatisfy(SpecificationRequiresFirstLetterToBeUpperCase.class)
- String lastName
- ) {
- setLastName(lastName);
- }
+ <para>For example:</para>
+ <programlisting format="linespecific">public class Customer {
+ @DescribedAs("Places an order, causing a shipping note "+
+ "to be generated and invoice to be dispatched")
+ public void createOrder() { ... }
+ ...
}</programlisting>
+ </sect2>
+ </sect1>
- <para>To help you write your own
- <classname>Specification</classname>s, there are some adapter classes
- in <package>org.apache.isis.applib.specs</package>:</para>
+ <sect1>
+ <title>How to specify the icon for an individual object's
+ state</title>
- <itemizedlist>
- <listitem>
- <para><classname>AbstractSpecification</classname>, which
- implements <classname>Specification</classname> and takes
- inspiration from the <ulink
- url="http://code.google.com/p/hamcrest/">Hamcrest</ulink>
- library's <classname>TypeSafeMatcher</classname> class</para>
- </listitem>
+ <para>As discussed in <xref
+ linkend="sec.HowToSpecifyTheIconForAnObjectsClass" />, the
+ <code>iconName()</code> method may be used to specify an object. The
+ value returned from this method need not be static, and so it can be
+ used to represent the state of an individual object.</para>
- <listitem>
- <para><classname>SpecificationAnd</classname>, which allows a set
- of <classname>Specification</classname>s to be grouped together
- and require that <emphasis>all</emphasis> of them are
- satisfied</para>
- </listitem>
+ <para>For example, an instance of <classname>Product</classname> could
+ use a photograph of the product as an icon, using:</para>
- <listitem>
- <para><classname>SpecificationOr</classname>, which allows a set
- of <classname>Specification</classname>s to be grouped together
- and require that <emphasis>at least one</emphasis> of them is
- satisfied</para>
- </listitem>
+ <programlisting format="linespecific">public class Product {
+ public String iconName() {
+ return "Product-" + getPhotograph();
+ }
+ ...
+}</programlisting>
- <listitem>
- <para><classname>SpecificationNot</classname>, which requires that
- the provided <classname>Specification</classname> is
- <emphasis>not</emphasis> satisfied</para>
- </listitem>
- </itemizedlist>
+ <para>Alternatively, an <classname>Order</classname> might vary the
+ icon according to the status of the object:<programlisting>public class Order {
+ public String iconName() {
+ return "Order-" + getStatus();
+ }
+ ...
+}</programlisting></para>
</sect1>
+ </chapter>
- <sect1 id="sec.BusinessRulesForCertainUsersOrRoles">
- <title>Hiding, disabling or validating for specific users or
- roles</title>
-
- <para>Generally it is not good practice to embed knowledge of roles
- and/or users into the domain classes; instead, this should be the
- responsibility of the framework or platform and should be specified
- and administered externally to the domain model. However, in rare
- circumstances it might be necessary or pragmatic to implement access
- control within the domain model.</para>
-
- <para>The current user can be obtained from
- <classname>DomainObjectContainer</classname>, using its
- <methodname>getUser()</methodname> method. Alternatively, if the
- domain object inherits from
- <classname>AbstractDomainObject</classname>, then
- <methodname>getUser()</methodname> is also inherited. In either case
- the method returns an object of type
- <methodname>org.apache.isis.security.UserMemento</methodname>, which
- holds both username and the set of roles for that user. The full
- details of the security classes can be found in <xref
- linkend="apx.SecurityClasses" />.</para>
+ <chapter>
+ <title>How to deal with errors</title>
- <para>The mechanism to apply a business rule is just to return an
- appropriate value from a supporting
- <methodname>hideXxx()</methodname>,
- <methodname>disableXxx()</methodname> or
- <methodname>validateXxx()</methodname> method.</para>
+ <abstract>
+ <para>How to inform the user if an error occurs.</para>
+ </abstract>
- <para>For example, the following requires that the MODIFY_SALARY role
- is assigned to the current user in order to update a salary property
- beyond a certain value:</para>
+ <para>Things go wrong. Isis handles many of the usual error conditions,
+ but your app may also wish to notify the user also when something goes
+ awry.</para>
- <programlisting format="linespecific">public class Employee extends AbstractDomainObject {
- public BigDecimal getSalary() { ... }
- public void setSalary(BigDecimal salary) { ... }
- public String validateSalary() {
- return salary.doubleValue() >= 30000 &&
- !getUser().hasRole("MODIFY_SALARY")?
- "Need MODIFY_SALARY role to increase salary above 30000": null;
- }
-}</programlisting>
- </sect1>
-
- <sect1>
+ <sect1 id="sec.passMessagesAndErrors">
<title>How to pass a messages and errors back to the user</title>
<para>Sometimes, within an action it is necessary or desirable to pass
@@ -2475,85 +2587,92 @@ public class Department {
viewer should again require the user to acknowledge the message,
and quite possibly indicate further steps that the user should
perform (eg notify the help desk).</para>
+
+ <para>In this last case, no changes will be made to any objects
+ (the transaction is aborted).</para>
</listitem>
</itemizedlist>
<para>The precise mechanics of how each of these messages is rendered
visible to the user is determined by the viewer being used.</para>
</sect1>
+
+ <sect1>
+ <title>How to deal with an unexpected error</title>
+
+ <para>An alternative to calling
+ <code>DomainObjectContainer#raiseError()</code> (see <xref
+ linkend="sec.passMessagesAndErrors" />) is to simply throw an
+ org.apache.isis.applib.ApplicationException. Which you use is a matter
+ of style, because the behaviour is exactly the same; internally
+ <code>raiseError()</code> just throws the
+ <code>ApplicationException</code>.</para>
+ </sect1>
</chapter>
<chapter>
- <title>More advanced how-tos</title>
+ <title>How to handle entity persistence lifecycle</title>
- <para></para>
+ <abstract>
+ <para>How to hook into the entity persistence lifecycle and handle
+ specific scenarios</para>
+ </abstract>
- <sect1>
- <title>How to specify the icon for an individual object's
- state</title>
+ <para>Isis automatically persists domain entities, performing both lazy
+ loading and dirty object tracking. As an application programmer you can
+ get visibility into and influence this behaviour.</para>
- <para>As discussed in <xref
- linkend="sec.HowToSpecifyTheIconForAnObjectsClass" />, the
- <code>iconName()</code> method may be used to specify an object. The
- value returned from this method need not be static, and so it can be
- used to represent the state of an individual object.</para>
+ <sect1 id="sec.DefaultPropertyValue">
+ <title>How to set up the initial value of a property
+ programmatically</title>
- <para>For example, an instance of <classname>Product</classname> could
- use a photograph of the product as an icon, using:</para>
+ <para>After an object has been created (see <xref
+ linkend="sec.HowToCreateAnObject" />), there are several different
+ ways to setup the initial values for an object's properties.</para>
- <programlisting format="linespecific">public class Product {
- public String iconName() {
- return "Product-" + getPhotograph();
- }
- ...
-}</programlisting>
+ <sect2>
+ <title>By each property's default values</title>
- <para>Alternatively, an <classname>Order</classname> might vary the
- icon according to the status of the object:<programlisting>public class Order {
- public String iconName() {
- return "Order-" + getStatus();
- }
- ...
-}</programlisting></para>
- </sect1>
+ <para>Firstly, the default value for a property can be supplied
+ using a supporting <methodname>defaultXxx()</methodname> method. The
+ syntax for specifying a default value is:</para>
- <sect1 id="sec.ResolveAndObjectChanged">
- <title>How to perform lazy loading (generally done
- automatically)</title>
+ <para><programlisting>public PropertyType defaultPropertyName()</programlisting></para>
- <para>The <classname>DomainObjectContainer</classname> provides the
- <methodname>resolve()</methodname> method in order to lazily resolve
- the value of a property or a collection. In earlier versions of the
- framework it was necessary to call this method prior to accessing or
- mutating any property or collection. This is no longer required
- because <emphasis>Apache Isis</emphasis> uses bytecode enhancement to
- automatically call this method.</para>
+ <para>where <literal moreinfo="none">PropertyType</literal> is the
+ same type as that of the property itself.</para>
+
+ <programlisting format="linespecific">public class Order {
+ public Address getShippingAddress() { ... }
+ public void setShippingAddress() { ... }
+ public Address defaultShippingAddress() {
+ return getCustomer().normalAddress();
+ }
+ ...
+}</programlisting>
+ </sect2>
- <para>While it is possible to disable this bytecode enhancement using
- <filename>isis.properties</filename> file, this is not generally
- recommended. If it is disabled then the
- <methodname>resolve()</methodname> method may need to be called
- manually.</para>
- </sect1>
+ <sect2>
+ <title>By the <methodname>created()</methodname> lifecycle
+ method</title>
- <sect1>
- <title>How to perform dirty object tracking (generally done
- automatically)</title>
+ <para>Alternatively, the domain object may choose to initialize its
+ property values in the <literal moreinfo="none">created()</literal>
+ lifecycle method (see <xref linkend="sec.LifecycleMethods" />). This
+ is called after any <methodname>defaultXxx()</methodname> methods
+ are called.</para>
+ </sect2>
- <para>The <classname>DomainObjectContainer</classname> provides the
- <methodname>objectChanged()</methodname> method in order to mark an
- object's state as having changed, and therefore requiring an update to
- the persistent object store. In earlier versions of the framework it
- was necessary to call this method after mutating any the property or
- collection. This is no longer required because <emphasis>Apache
- Isis</emphasis> uses bytecode enhancement to automatically call this
- method.</para>
+ <sect2>
+ <title>Programmatically, by the creator</title>
- <para>While it is possible to disable this bytecode enhancement using
- <filename>isis.properties</filename> file, this is not generally
- recommended. If it is disabled then the
- <methodname>objectChanged()</methodname> method may need to be called
- manually.</para>
+ <para>Third, and perhaps most obviously, the creator of the object
+ could initialize the properties of the object immediately after
+ calling <methodname>newTransientInstance(...)</methodname>. This
+ would be appropriate if the creator had reason to override any
+ values setup in the <methodname>defaultXxx()</methodname> or
+ <methodname>created()</methodname> methods discussed above.</para>
+ </sect2>
</sect1>
<sect1 id="sec.LifecycleMethods">
@@ -2720,57 +2839,168 @@ public class Department {
</warning>
</sect1>
- <sect1 id="sec.DefaultPropertyValue">
- <title>How to set up the initial value of a property
- programmatically</title>
+ <sect1>
+ <title condition="vb">How to specify that an object should not be
+ persisted</title>
- <para>After an object has been created (see <xref
- linkend="sec.HowToCreateAnObject" />), there are several different
- ways to setup the initial values for an object's properties.</para>
+ <para>Non-persisted objects are intended to be used as view models;
+ they aggregate some state with respect to a certain process. This may
+ be read-only (eg a projection of certain informaiton) or read-write
+ (eg a wizard-like process object). Either way, the viewer is expected
+ to interpret this by not providing any sort of automatic "save" menu
+ item if such an object is returned to the
+ <acronym>GUI</acronym>.</para>
- <sect2>
- <title>By each property's default values</title>
+ <para>Non-persisted objects that are read-only are typically also
+ marked as immutable (see <xref linkend="sec.Immutable" />).</para>
- <para>Firstly, the default value for a property can be supplied
- using a supporting <methodname>defaultXxx()</methodname> method. The
- syntax for specifying a default value is:</para>
+ <para>To indicate that an object cannot be persisted, use the <literal
+ moreinfo="none">@NotPersistable</literal> annotation.</para>
+ </sect1>
- <para><programlisting>public PropertyType defaultPropertyName()</programlisting></para>
+ <sect1 id="sec.ResolveAndObjectChanged">
+ <title>How to perform lazy loading (generally done
+ automatically)</title>
- <para>where <literal moreinfo="none">PropertyType</literal> is the
- same type as that of the property itself.</para>
+ <para>The <classname>DomainObjectContainer</classname> provides the
+ <methodname>resolve()</methodname> method in order to lazily resolve
+ the value of a property or a collection. In earlier versions of the
+ framework it was necessary to call this method prior to accessing or
+ mutating any property or collection. This is no longer required
+ because <emphasis>Apache Isis</emphasis> uses bytecode enhancement to
+ automatically call this method.</para>
- <programlisting format="linespecific">public class Order {
- public Address getShippingAddress() { ... }
- public void setShippingAddress() { ... }
- public Address defaultShippingAddress() {
- return getCustomer().normalAddress();
+ <para>While it is possible to disable this bytecode enhancement using
+ <filename>isis.properties</filename> file, this is not generally
+ recommended. If it is disabled then the
+ <methodname>resolve()</methodname> method may need to be called
+ manually.</para>
+ </sect1>
+
+ <sect1>
+ <title>How to perform dirty object tracking (generally done
+ automatically)</title>
+
+ <para>The <classname>DomainObjectContainer</classname> provides the
+ <methodname>objectChanged()</methodname> method in order to mark an
+ object's state as having changed, and therefore requiring an update to
+ the persistent object store. In earlier versions of the framework it
+ was necessary to call this method after mutating any the property or
+ collection. This is no longer required because <emphasis>Apache
+ Isis</emphasis> uses bytecode enhancement to automatically call this
+ method.</para>
+
+ <para>While it is possible to disable this bytecode enhancement using
+ <filename>isis.properties</filename> file, this is not generally
+ recommended. If it is disabled then the
+ <methodname>objectChanged()</methodname> method may need to be called
+ manually.</para>
+ </sect1>
+ </chapter>
+
+ <chapter>
+ <title>How to handle security concerns</title>
+
+ <abstract>
+ <para>Further validation how-to's that apply across all class
+ members</para>
+ </abstract>
+
+ <para>This chapter has some additional recipes/how-tos relating to
+ implementing business rules. They apply across all class members.</para>
+
+ <sect1 id="sec.BusinessRulesForCertainUsersOrRoles">
+ <title>Hiding, disabling or validating for specific users or
+ roles</title>
+
+ <para>Generally it is not good practice to embed knowledge of roles
+ and/or users into the domain classes; instead, this should be the
+ responsibility of the framework or platform and should be specified
+ and administered externally to the domain model. However, in rare
+ circumstances it might be necessary or pragmatic to implement access
+ control within the domain model.</para>
+
+ <para>The current user can be obtained from
+ <classname>DomainObjectContainer</classname>, using its
+ <methodname>getUser()</methodname> method. Alternatively, if the
+ domain object inherits from
+ <classname>AbstractDomainObject</classname>, then
+ <methodname>getUser()</methodname> is also inherited. In either case
+ the method returns an object of type
+ <methodname>org.apache.isis.security.UserMemento</methodname>, which
+ holds both username and the set of roles for that user. The full
+ details of the security classes can be found in <xref
+ linkend="apx.SecurityClasses" />.</para>
+
+ <para>The mechanism to apply a business rule is just to return an
+ appropriate value from a supporting
+ <methodname>hideXxx()</methodname>,
+ <methodname>disableXxx()</methodname> or
+ <methodname>validateXxx()</methodname> method.</para>
+
+ <para>For example, the following requires that the MODIFY_SALARY role
+ is assigned to the current user in order to update a salary property
+ beyond a certain value:</para>
+
+ <programlisting format="linespecific">public class Employee extends AbstractDomainObject {
+ public BigDecimal getSalary() { ... }
+ public void setSalary(BigDecimal salary) { ... }
+ public String validateSalary() {
+ return salary.doubleValue() >= 30000 &&
+ !getUser().hasRole("MODIFY_SALARY")?
+ "Need MODIFY_SALARY role to increase salary above 30000": null;
}
- ...
}</programlisting>
- </sect2>
+ </sect1>
- <sect2>
- <title>By the <methodname>created()</methodname> lifecycle
- method</title>
+ <sect1>
+ <title>How to use Isis' authorization manager</title>
- <para>Alternatively, the domain object may choose to initialize its
- property values in the <literal moreinfo="none">created()</literal>
- lifecycle method (see <xref linkend="sec.LifecycleMethods" />). This
- is called after any <methodname>defaultXxx()</methodname> methods
- are called.</para>
- </sect2>
+ <para>An alternative to hard-coding role names within the domain
+ entities is instead to configure an authorization manager. This is a
+ component that can either hide or disable access to class members
+ (pro
<TRUNCATED>