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 2015/03/03 21:23:12 UTC

[20/20] isis git commit: Merge commit 'f7284d8fb4fbe58479e819b37e7ee3cc731d57ad' into ISIS-789a

Merge commit 'f7284d8fb4fbe58479e819b37e7ee3cc731d57ad' into ISIS-789a

Merging Jeremy Branham's work on upgrading DN to 4.0.x.

Resolved conflicts:
	README.md
	core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusApplicationComponents.java
	example/application/simpleapp/webapp/src/main/webapp/WEB-INF/isis.properties
	mothballed/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItem.java
	mothballed/example/application/todoapp/webapp/src/main/webapp/WEB-INF/isis.properties


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/e2dffa84
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/e2dffa84
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/e2dffa84

Branch: refs/heads/ISIS-789
Commit: e2dffa844323f4ffcd715fe0f9a94326c58da21e
Parents: 55e569c f7284d8
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Tue Mar 3 19:43:07 2015 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Tue Mar 3 19:43:07 2015 +0000

----------------------------------------------------------------------
 ...DatanucleusPersistableTypesFacetFactory.java |  14 +
 .../RemoveDnPrefixedMethodsFacetFactory.java    |  28 ++
 .../classsubstitutor/ClassSubstitutor.java      |   4 +
 .../dflt/ProgrammingModelFacetsJava5.java       |   5 +
 core/pom.xml                                    |   6 +-
 .../IdentifierGeneratorForDataNucleus.java      |   4 +-
 .../system/persistence/PersistenceSession.java  |   2 +-
 .../DataNucleusApplicationComponents.java       |  30 +-
 .../jdo/datanucleus/DataNucleusObjectStore.java |  11 +-
 ...ataNucleusPersistenceMechanismInstaller.java |  18 +-
 .../IsisConfigurationForJdoIntegTests.java      |  11 +-
 .../jdo/datanucleus/JDOStateManagerForIsis.java |  93 ++++-
 .../persistence/FrameworkSynchronizer.java      |  42 +-
 .../persistence/IsisLifecycleListener.java      |  19 +-
 .../jdo/datanucleus/persistence/Utils.java      |  14 +-
 .../PersistenceQueryProcessorAbstract.java      |   9 +-
 .../persistence/spi/JdoObjectIdSerializer.java  |   8 +-
 .../datanucleus/valuetypes/IsisDateMapping.java |   9 +-
 .../valuetypes/IsisDateTimeMapping.java         |   9 +-
 example/application/neoapp/dom/.gitignore       |   2 +
 example/application/neoapp/dom/log4j.properties |  41 ++
 example/application/neoapp/dom/pom.xml          | 172 ++++++++
 .../dom/src/main/java/META-INF/persistence.xml  |  26 ++
 .../src/main/java/dom/simple/SimpleObject.java  |  76 ++++
 .../java/dom/simple/SimpleObject.layout.json    |  44 ++
 .../src/main/java/dom/simple/SimpleObject.png   | Bin 0 -> 557 bytes
 .../src/main/java/dom/simple/SimpleObjects.java |  63 +++
 .../test/java/dom/simple/SimpleObjectTest.java  |  51 +++
 .../test/java/dom/simple/SimpleObjectsTest.java | 102 +++++
 example/application/neoapp/fixture/.gitignore   |   3 +
 example/application/neoapp/fixture/pom.xml      |  40 ++
 .../simple/SimpleObjectsFixturesService.java    |  69 ++++
 .../simple/SimpleObjectsTearDownFixture.java    |  36 ++
 .../simple/objects/SimpleObjectAbstract.java    |  36 ++
 .../simple/objects/SimpleObjectForBar.java      |  31 ++
 .../simple/objects/SimpleObjectForBaz.java      |  31 ++
 .../simple/objects/SimpleObjectForFoo.java      |  31 ++
 .../simple/scenario/SimpleObjectsFixture.java   |  45 +++
 .../application/neoapp/integtests/.gitignore    |   3 +
 .../neoapp/integtests/logging.properties        | 103 +++++
 example/application/neoapp/integtests/pom.xml   | 159 ++++++++
 .../integration/SimpleAppSystemInitializer.java |  72 ++++
 .../integration/glue/BootstrappingGlue.java     |  53 +++
 .../integration/glue/CatalogOfFixturesGlue.java |  46 +++
 .../glue/InMemoryDBForSimpleApp.java            |  40 ++
 .../glue/simple/SimpleObjectGlue.java           |  96 +++++
 .../java/integration/specs/simple/RunSpecs.java |  38 ++
 .../SimpleObjectSpec_listAllAndCreate.feature   |  37 ++
 .../integration/tests/SimpleAppIntegTest.java   |  38 ++
 .../tests/smoke/SimpleObjectTest.java           |  82 ++++
 .../tests/smoke/SimpleObjectsTest.java          | 148 +++++++
 example/application/neoapp/pom.xml              | 378 ++++++++++++++++++
 example/application/neoapp/webapp/.gitignore    |   3 +
 .../launch/SimpleApp-PROTOTYPE-jrebel.launch    |  31 ++
 .../SimpleApp-PROTOTYPE-no-fixtures.launch      |  23 ++
 .../SimpleApp-PROTOTYPE-with-fixtures.launch    |  20 +
 .../launch/SimpleApp-SERVER-no-fixtures.launch  |  23 ++
 .../webapp/ide/intellij/launch/README.txt       |   2 +
 .../ide/intellij/launch/SimpleApp_PROTOTYPE.xml |  28 ++
 .../launch/SimpleApp__enhance_only_.xml         |  22 +
 .../application/neoapp/webapp/lib/.gitignore    |   5 +
 example/application/neoapp/webapp/messages.log  |   0
 example/application/neoapp/webapp/pom.xml       | 399 +++++++++++++++++++
 .../src/main/java/webapp/SimpleApplication.java | 150 +++++++
 .../src/main/jettyconsole/isis-banner.pdn       | Bin 0 -> 69658 bytes
 .../src/main/jettyconsole/isis-banner.png       | Bin 0 -> 30776 bytes
 .../src/main/resources/webapp/welcome.html      |  35 ++
 .../src/main/webapp/WEB-INF/isis.properties     | 232 +++++++++++
 .../src/main/webapp/WEB-INF/logging.properties  | 185 +++++++++
 .../main/webapp/WEB-INF/persistor.properties    | 128 ++++++
 .../WEB-INF/persistor_datanucleus.properties    |  86 ++++
 .../webapp/src/main/webapp/WEB-INF/shiro.ini    |  93 +++++
 .../WEB-INF/viewer_restfulobjects.properties    |  66 +++
 .../webapp/WEB-INF/viewer_wicket.properties     |  84 ++++
 .../webapp/src/main/webapp/WEB-INF/web.xml      | 309 ++++++++++++++
 .../src/main/webapp/about/images/isis-logo.png  | Bin 0 -> 14160 bytes
 .../webapp/src/main/webapp/about/index.html     | 104 +++++
 .../webapp/src/main/webapp/css/application.css  |  19 +
 .../src/main/webapp/images/spinning-icon.gif    | Bin 0 -> 5266 bytes
 .../src/main/webapp/scripts/application.js      |   3 +
 .../webapp/src/test/resources/NeoBrowser.PNG    | Bin 0 -> 39360 bytes
 example/application/simpleapp/webapp/pom.xml    |  16 +-
 .../src/main/webapp/WEB-INF/logging.properties  |   4 +-
 .../WEB-INF/persistor_datanucleus.properties    |   9 +-
 .../dom/src/main/java/dom/todo/ToDoItem.java    |   9 +-
 .../integration/tests/ToDoItemIntegTest.java    |   2 +-
 .../example/application/todoapp/webapp/pom.xml  |  16 +-
 .../src/main/webapp/WEB-INF/isis.properties     |   4 +-
 .../WEB-INF/persistor_datanucleus.properties    |   8 +-
 pom.xml                                         |   9 +
 90 files changed, 4523 insertions(+), 132 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/pom.xml
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/IdentifierGeneratorForDataNucleus.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusApplicationComponents.java
----------------------------------------------------------------------
diff --cc core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusApplicationComponents.java
index fe5b82e,cbcf222..56fcb3b
--- a/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusApplicationComponents.java
+++ b/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusApplicationComponents.java
@@@ -24,11 -25,16 +25,14 @@@ import java.util.Set
  import javax.jdo.JDOHelper;
  import javax.jdo.PersistenceManager;
  import javax.jdo.PersistenceManagerFactory;
 -
  import com.google.common.base.Joiner;
  import com.google.common.collect.Maps;
 -
  import org.datanucleus.NucleusContext;
+ import org.datanucleus.NucleusContextHelper;
+ import org.datanucleus.PropertyNames;
+ import org.datanucleus.StoreNucleusContext;
  import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
 +import org.datanucleus.metadata.MetaDataListener;
  import org.datanucleus.metadata.MetaDataManager;
  import org.datanucleus.store.StoreManager;
  import org.datanucleus.store.schema.SchemaAwareStoreManager;
@@@ -101,30 -104,36 +105,40 @@@ public class DataNucleusApplicationComp
          instance = this;
      }
  
 -    private void init(final Map<String, String> props, final Set<String> persistableClassNameSet) {
 +    private void initialize() {
          final String persistableClassNames = Joiner.on(',').join(persistableClassNameSet);
          
 -        props.put("datanucleus.autoStartClassNames", persistableClassNames);
 -        persistenceManagerFactory = JDOHelper.getPersistenceManagerFactory(props);
 +        datanucleusProps.put("datanucleus.autoStartClassNames", persistableClassNames);
 +        persistenceManagerFactory = JDOHelper.getPersistenceManagerFactory(datanucleusProps);
  
-         final boolean createSchema = Boolean.parseBoolean(datanucleusProps.get("datanucleus.autoCreateSchema"));
 -        final boolean createSchema = (Boolean.parseBoolean( props.get(PropertyNames.PROPERTY_SCHEMA_AUTOCREATE_SCHEMA) ) ||
 -                Boolean.parseBoolean( props.get(PropertyNames.PROPERTY_SCHEMA_AUTOCREATE_ALL) ));
++        final boolean createSchema = (Boolean.parseBoolean( datanucleusProps.get(PropertyNames.PROPERTY_SCHEMA_AUTOCREATE_SCHEMA) ) ||
++                Boolean.parseBoolean( datanucleusProps.get(PropertyNames.PROPERTY_SCHEMA_AUTOCREATE_ALL) ));
          if(createSchema) {
 -            createSchema(props, persistableClassNameSet);
 +            createSchema();
          }
  
          namedQueryByName = catalogNamedQueries(persistableClassNameSet);
 -
      }
      
++
+     public PersistenceManagerFactory getPersistenceManagerFactory() {
+         return persistenceManagerFactory;
+     }
+ 
 -    private void createSchema(final Map<String, String> props, final Set<String> classesToBePersisted) {
 +    private void createSchema() {
+     	//REF: http://www.datanucleus.org/products/datanucleus/jdo/schema.html
+     	
          final JDOPersistenceManagerFactory jdopmf = (JDOPersistenceManagerFactory)persistenceManagerFactory;
          final NucleusContext nucleusContext = jdopmf.getNucleusContext();
-         final StoreManager storeManager = nucleusContext.getStoreManager();
-         final MetaDataManager metaDataManager = nucleusContext.getMetaDataManager();
- 
-         registerMetadataListener(metaDataManager);
-         if (storeManager instanceof SchemaAwareStoreManager) {
-             final SchemaAwareStoreManager schemaAwareStoreManager = (SchemaAwareStoreManager) storeManager;
-             schemaAwareStoreManager.createSchema(persistableClassNameSet, asProperties(datanucleusProps));
+         if (nucleusContext instanceof StoreNucleusContext) {
+             final StoreManager storeManager = ((StoreNucleusContext)nucleusContext).getStoreManager();
++        	final MetaDataManager metaDataManager = nucleusContext.getMetaDataManager();
++
++	        registerMetadataListener(metaDataManager);
+             if (storeManager instanceof SchemaAwareStoreManager) {
 -                ((SchemaAwareStoreManager)storeManager).createSchemaForClasses(classesToBePersisted, asProperties(props));
++    	        final SchemaAwareStoreManager schemaAwareStoreManager = (SchemaAwareStoreManager) storeManager;
++        	    schemaAwareStoreManager.createSchema(classesToBePersisted, asProperties(props));
+             }
          }
      }
  

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
----------------------------------------------------------------------
diff --cc core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
index 828115d,fdf36b8..013041e
--- a/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
+++ b/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
@@@ -72,18 -70,12 +74,19 @@@ import org.apache.isis.objectstore.jdo.
  import org.apache.isis.objectstore.jdo.datanucleus.persistence.commands.DataNucleusCreateObjectCommand;
  import org.apache.isis.objectstore.jdo.datanucleus.persistence.commands.DataNucleusDeleteObjectCommand;
  import org.apache.isis.objectstore.jdo.datanucleus.persistence.commands.DataNucleusUpdateObjectCommand;
 -import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.*;
 +import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.PersistenceQueryFindAllInstancesProcessor;
 +import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.PersistenceQueryFindByPatternProcessor;
 +import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.PersistenceQueryFindByTitleProcessor;
 +import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.PersistenceQueryFindUsingApplibQueryProcessor;
 +import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.PersistenceQueryProcessor;
 +import org.apache.isis.objectstore.jdo.datanucleus.persistence.queries.QueryUtil;
  import org.apache.isis.objectstore.jdo.datanucleus.persistence.spi.JdoObjectIdSerializer;
  import org.apache.isis.objectstore.jdo.metamodel.facets.object.query.JdoNamedQuery;
+ import org.datanucleus.enhancer.Persistable;
  
 -import static org.apache.isis.core.commons.ensure.Ensure.*;
 +import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
 +import static org.apache.isis.core.commons.ensure.Ensure.ensureThatContext;
 +import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState;
  import static org.hamcrest.CoreMatchers.is;
  import static org.hamcrest.CoreMatchers.notNullValue;
  

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusPersistenceMechanismInstaller.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/IsisConfigurationForJdoIntegTests.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/spi/JdoObjectIdSerializer.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/example/application/simpleapp/webapp/pom.xml
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/logging.properties
----------------------------------------------------------------------
diff --cc example/application/simpleapp/webapp/src/main/webapp/WEB-INF/logging.properties
index 62fd8ea,3b5235a..dc32391
--- a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/logging.properties
+++ b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/logging.properties
@@@ -183,5 -181,5 +183,5 @@@ log4j.additivity.log4j.logger.org.apach
  
  
  # Application-specific logging
 -log4j.logger.dom.todo.ToDoItem=DEBUG, Stderr
 -log4j.additivity.dom.todo.ToDoItem=false
 +log4j.logger.dom.simple.SimpleObject=DEBUG, Stderr
- log4j.additivity.dom.simple.SimpleObject=false
++log4j.additivity.dom.simple.SimpleObject=false

http://git-wip-us.apache.org/repos/asf/isis/blob/e2dffa84/mothballed/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItem.java
----------------------------------------------------------------------
diff --cc mothballed/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItem.java
index 75f5583,0000000..679e4cd
mode 100644,000000..100644
--- a/mothballed/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItem.java
+++ b/mothballed/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItem.java
@@@ -1,979 -1,0 +1,982 @@@
 +/*
 + *  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.
 + */
 +package dom.todo;
 +
 +import java.math.BigDecimal;
 +import java.net.MalformedURLException;
 +import java.net.URL;
 +import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.Comparator;
 +import java.util.List;
 +import java.util.Set;
 +import java.util.SortedSet;
 +import java.util.TreeSet;
 +import javax.jdo.JDOHelper;
 +import javax.jdo.annotations.IdentityType;
 +import javax.jdo.annotations.VersionStrategy;
 +import com.google.common.base.Objects;
 +import com.google.common.base.Predicate;
 +import com.google.common.collect.Ordering;
++import org.datanucleus.enhancer.Persistable;
 +import org.joda.time.LocalDate;
 +import org.apache.isis.applib.DomainObjectContainer;
 +import org.apache.isis.applib.Identifier;
 +import org.apache.isis.applib.NonRecoverableException;
 +import org.apache.isis.applib.RecoverableException;
 +import org.apache.isis.applib.annotation.Action;
 +import org.apache.isis.applib.annotation.BookmarkPolicy;
 +import org.apache.isis.applib.annotation.Collection;
 +import org.apache.isis.applib.annotation.CollectionLayout;
 +import org.apache.isis.applib.annotation.DomainObject;
 +import org.apache.isis.applib.annotation.DomainObjectLayout;
 +import org.apache.isis.applib.annotation.Editing;
 +import org.apache.isis.applib.annotation.RestrictTo;
 +import org.apache.isis.applib.annotation.InvokeOn;
 +import org.apache.isis.applib.annotation.InvokedOn;
 +import org.apache.isis.applib.annotation.Optionality;
 +import org.apache.isis.applib.annotation.Parameter;
 +import org.apache.isis.applib.annotation.ParameterLayout;
 +import org.apache.isis.applib.annotation.Programmatic;
 +import org.apache.isis.applib.annotation.Property;
 +import org.apache.isis.applib.annotation.SemanticsOf;
 +import org.apache.isis.applib.annotation.Where;
 +import org.apache.isis.applib.security.UserMemento;
 +import org.apache.isis.applib.services.actinvoc.ActionInvocationContext;
 +import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
 +import org.apache.isis.applib.services.eventbus.EventBusService;
 +import org.apache.isis.applib.services.scratchpad.Scratchpad;
 +import org.apache.isis.applib.services.wrapper.HiddenException;
 +import org.apache.isis.applib.services.wrapper.WrapperFactory;
 +import org.apache.isis.applib.util.ObjectContracts;
 +import org.apache.isis.applib.util.TitleBuffer;
 +import org.apache.isis.applib.value.Blob;
 +import org.apache.isis.applib.value.Clob;
 +
 +@javax.jdo.annotations.PersistenceCapable(identityType=IdentityType.DATASTORE)
 +@javax.jdo.annotations.DatastoreIdentity(
 +        strategy=javax.jdo.annotations.IdGeneratorStrategy.IDENTITY,
 +         column="id")
 +@javax.jdo.annotations.Version(
 +        strategy=VersionStrategy.VERSION_NUMBER, 
 +        column="version")
 +@javax.jdo.annotations.Uniques({
 +    @javax.jdo.annotations.Unique(
 +            name="ToDoItem_description_must_be_unique", 
 +            members={"ownedBy","description"})
 +})
 +@javax.jdo.annotations.Queries( {
 +    @javax.jdo.annotations.Query(
 +            name = "findByOwnedBy", language = "JDOQL",
 +            value = "SELECT "
 +                    + "FROM dom.todo.ToDoItem "
 +                    + "WHERE ownedBy == :ownedBy"),
 +    @javax.jdo.annotations.Query(
 +            name = "findByOwnedByAndCompleteIsFalse", language = "JDOQL",
 +            value = "SELECT "
 +                    + "FROM dom.todo.ToDoItem "
 +                    + "WHERE ownedBy == :ownedBy "
 +                    + "   && complete == false"),
 +    @javax.jdo.annotations.Query(
 +            name = "findByOwnedByAndCompleteIsTrue", language = "JDOQL",
 +            value = "SELECT "
 +                    + "FROM dom.todo.ToDoItem "
 +                    + "WHERE ownedBy == :ownedBy "
 +                    + "&& complete == true"),
 +    @javax.jdo.annotations.Query(
 +            name = "findByOwnedByAndCategory", language = "JDOQL",
 +            value = "SELECT "
 +                    + "FROM dom.todo.ToDoItem "
 +                    + "WHERE ownedBy == :ownedBy "
 +                    + "&& category == :category"),
 +    @javax.jdo.annotations.Query(
 +            name = "findByOwnedByAndDescriptionContains", language = "JDOQL",
 +            value = "SELECT "
 +                    + "FROM dom.todo.ToDoItem "
 +                    + "WHERE ownedBy == :ownedBy && "
 +                    + "description.indexOf(:description) >= 0")
 +})
 +@DomainObject(
 +        autoCompleteRepository = ToDoItems.class, // for drop-downs, unless autoCompleteNXxx() is present
 +        autoCompleteAction = "autoComplete",
 +        // bounded = true,  // for drop-downs if only a small number of instances only (overrides autoComplete)
 +        objectType = "TODO"
 +)
 +@DomainObjectLayout(
 +        bookmarking = BookmarkPolicy.AS_ROOT
 +)
 +public class ToDoItem implements Categorized, Comparable<ToDoItem> {
 +
 +    //region > LOG
 +    /**
 +     * It isn't common for entities to log, but they can if required.  
 +     * Isis uses slf4j API internally (with log4j as implementation), and is the recommended API to use. 
 +     */
 +    private final static org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(ToDoItem.class);
 +    //endregion
 +
 +    // region > title, icon
 +    public String title() {
 +        final TitleBuffer buf = new TitleBuffer();
 +        buf.append(getDescription());
 +        if (isComplete()) {
 +            buf.append("- Completed!");
 +        } else {
 +            try {
 +                final LocalDate dueBy = wrapperFactory.wrap(this).getDueBy();
 +                if (dueBy != null) {
 +                    buf.append(" due by", dueBy);
 +                }
 +            } catch(final HiddenException ignored) {
 +            }
 +        }
 +        return buf.toString();
 +    }
 +    
 +    public String iconName() {
 +        return !isComplete() ? "todo" : "done";
 +    }
 +
 +    public String cssClass() { return iconName(); }
 +
 +    //endregion
 +
 +    //region > description (property)
 +    private String description;
 +
 +    @javax.jdo.annotations.Column(allowsNull="false", length=100)
 +    @Property(
 +        regexPattern = "\\w[@&:\\-\\,\\.\\+ \\w]*"
 +    )
 +    public String getDescription() {
 +        return description;
 +    }
 +
 +    public void setDescription(final String description) {
 +        this.description = description;
 +    }
 +    public void modifyDescription(final String description) {
 +        setDescription(description);
 +    }
 +    public void clearDescription() {
 +        setDescription(null);
 +    }
 +    //endregion
 +
 +    //region > dueBy (property)
 +    @javax.jdo.annotations.Persistent(defaultFetchGroup="true")
 +    private LocalDate dueBy;
 +
 +    @javax.jdo.annotations.Column(allowsNull="true")
 +    public LocalDate getDueBy() {
 +        return dueBy;
 +    }
 +
 +    /**
 +     * Demonstrates how to perform security checks within the domain code.
 +     *
 +     * <p>
 +     *     Generally speaking this approach is not recommended; such checks should
 +     *     wherever possible be externalized in the security subsystem.
 +     * </p>
 +     */
 +    public boolean hideDueBy() {
 +        final UserMemento user = container.getUser();
 +        return user.hasRole("realm1:noDueBy_role");
 +    }
 +
 +    public void setDueBy(final LocalDate dueBy) {
 +        this.dueBy = dueBy;
 +    }
 +    public void clearDueBy() {
 +        setDueBy(null);
 +    }
 +    // proposed new value is validated before setting
 +    public String validateDueBy(final LocalDate dueBy) {
 +        if (dueBy == null) {
 +            return null;
 +        }
 +        return toDoItems.validateDueBy(dueBy);
 +    }
 +    //endregion
 +
 +    //region > category and subcategory (property)
 +
 +    public static enum Category {
 +        Professional {
 +            @Override
 +            public List<Subcategory> subcategories() {
 +                return Arrays.asList(null, Subcategory.OpenSource, Subcategory.Consulting, Subcategory.Education, Subcategory.Marketing);
 +            }
 +        }, Domestic {
 +            @Override
 +            public List<Subcategory> subcategories() {
 +                return Arrays.asList(null, Subcategory.Shopping, Subcategory.Housework, Subcategory.Garden, Subcategory.Chores);
 +            }
 +        }, Other {
 +            @Override
 +            public List<Subcategory> subcategories() {
 +                return Arrays.asList(null, Subcategory.Other);
 +            }
 +        };
 +        
 +        public abstract List<Subcategory> subcategories();
 +    }
 +
 +    public static enum Subcategory {
 +        // professional
 +        OpenSource, Consulting, Education, Marketing,
 +        // domestic
 +        Shopping, Housework, Garden, Chores,
 +        // other
 +        Other;
 +
 +        public static List<Subcategory> listFor(final Category category) {
 +            return category != null? category.subcategories(): Collections.<Subcategory>emptyList();
 +        }
 +
 +        static String validate(final Category category, final Subcategory subcategory) {
 +            if(category == null) {
 +                return "Enter category first";
 +            }
 +            return !category.subcategories().contains(subcategory) 
 +                    ? "Invalid subcategory for category '" + category + "'" 
 +                    : null;
 +        }
 +        
 +        public static Predicate<Subcategory> thoseFor(final Category category) {
 +            return new Predicate<Subcategory>() {
 +
 +                @Override
 +                public boolean apply(final Subcategory subcategory) {
 +                    return category.subcategories().contains(subcategory);
 +                }
 +            };
 +        }
 +    }
 +
 +    // //////////////////////////////////////
 +
 +    private Category category;
 +
 +    @javax.jdo.annotations.Column(allowsNull="false")
 +    @Property(
 +            editing = Editing.DISABLED,
 +            editingDisabledReason = "Use action to update both category and subcategory"
 +    )
 +    public Category getCategory() {
 +        return category;
 +    }
 +
 +    public void setCategory(final Category category) {
 +        this.category = category;
 +    }
 +
 +    // //////////////////////////////////////
 +
 +    private Subcategory subcategory;
 +
 +    @javax.jdo.annotations.Column(allowsNull="true")
 +    @Property(
 +            editing = Editing.DISABLED,
 +            editingDisabledReason = "Use action to update both category and subcategory"
 +    )
 +    public Subcategory getSubcategory() {
 +        return subcategory;
 +    }
 +    public void setSubcategory(final Subcategory subcategory) {
 +        this.subcategory = subcategory;
 +    }
 +    //endregion
 +
 +    //region > ownedBy (property)
 +
 +    private String ownedBy;
 +
 +    @javax.jdo.annotations.Column(allowsNull="false")
 +    public String getOwnedBy() {
 +        return ownedBy;
 +    }
 +
 +    public void setOwnedBy(final String ownedBy) {
 +        this.ownedBy = ownedBy;
 +    }
 +    //endregion
 +
 +    //region > complete (property), completed (action), notYetCompleted (action)
 +
 +    private boolean complete;
 +
 +    @Property(
 +        editing = Editing.DISABLED
 +    )
 +    public boolean isComplete() {
 +        return complete;
 +    }
 +
 +    public void setComplete(final boolean complete) {
 +        this.complete = complete;
 +    }
 +
 +    @Action(
 +            domainEvent =CompletedEvent.class,
 +            invokeOn = InvokeOn.OBJECT_AND_COLLECTION
 +    )
 +    public ToDoItem completed() {
 +        setComplete(true);
 +        
 +        //
 +        // remainder of method just demonstrates the use of the Bulk.InteractionContext service 
 +        //
 +        @SuppressWarnings("unused")
 +        final List<Object> allObjects = actionInvocationContext.getDomainObjects();
 +        
 +        LOG.debug("completed: "
 +                + actionInvocationContext.getIndex() +
 +                " [" + actionInvocationContext.getSize() + "]"
 +                + (actionInvocationContext.isFirst() ? " (first)" : "")
 +                + (actionInvocationContext.isLast() ? " (last)" : ""));
 +
 +        // if invoked as a regular action, return this object;
 +        // otherwise (if invoked as bulk), return null (so go back to the list)
 +        return actionInvocationContext.getInvokedOn() == InvokedOn.OBJECT? this: null;
 +    }
 +    // disable action dependent on state of object
 +    public String disableCompleted() {
 +        return isComplete() ? "Already completed" : null;
 +    }
 +
 +    @Action(
 +        invokeOn = InvokeOn.OBJECT_AND_COLLECTION
 +    )
 +    public ToDoItem notYetCompleted() {
 +        setComplete(false);
 +
 +        // if invoked as a regular action, return this object;
 +        // otherwise (if invoked as bulk), return null (so go back to the list)
 +        return actionInvocationContext.getInvokedOn() == InvokedOn.OBJECT ? this: null;
 +    }
 +    // disable action dependent on state of object
 +    public String disableNotYetCompleted() {
 +        return !complete ? "Not yet completed" : null;
 +    }
 +    //endregion
 +
 +    //region > completeSlowly (property)
 +    // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 +    @Action(
 +            hidden = Where.EVERYWHERE
 +    )
 +    public void completeSlowly(final int millis) {
 +        try {
 +            Thread.sleep(millis);
 +        } catch (final InterruptedException ignored) {
 +        }
 +        setComplete(true);
 +    }
 +    //endregion
 +
 +    //region > cost (property), updateCost (action)
 +    private BigDecimal cost;
 +
 +    @javax.jdo.annotations.Column(allowsNull="true", scale=2)
 +    @javax.validation.constraints.Digits(integer=10, fraction=2)
 +    @Property(
 +            editing = Editing.DISABLED,
 +            editingDisabledReason = "Update using action"
 +    )
 +    public BigDecimal getCost() {
 +        return cost;
 +    }
 +
 +    public void setCost(final BigDecimal cost) {
 +        this.cost = cost!=null?cost.setScale(2, BigDecimal.ROUND_HALF_EVEN):null;
 +    }
 +
 +    @Action(
 +            semantics = SemanticsOf.IDEMPOTENT
 +    )
 +    public ToDoItem updateCost(
 +            @Parameter(optionality = Optionality.OPTIONAL)
 +            @ParameterLayout(named = "New cost")
 +            @javax.validation.constraints.Digits(integer=10, fraction=2)
 +            final BigDecimal cost) {
 +        LOG.debug("%s: cost updated: %s -> %s", container.titleOf(this), getCost(), cost);
 +        
 +        // just to simulate a long-running action
 +        try {
 +            Thread.sleep(3000);
 +        } catch (final InterruptedException ignored) {
 +        }
 +        
 +        setCost(cost);
 +        return this;
 +    }
 +
 +    // provide a default value for argument #0
 +    public BigDecimal default0UpdateCost() {
 +        return getCost();
 +    }
 +    
 +    // validate action arguments
 +    public String validateUpdateCost(final BigDecimal proposedCost) {
 +        if(proposedCost == null) { return null; }
 +        return proposedCost.compareTo(BigDecimal.ZERO) < 0? "Cost must be positive": null;
 +    }
 +    //endregion
 +
 +    //region > notes (property)
 +    private String notes;
 +
 +    @javax.jdo.annotations.Column(allowsNull="true", length=400)
 +    public String getNotes() {
 +        return notes;
 +    }
 +
 +    public void setNotes(final String notes) {
 +        this.notes = notes;
 +    }
 +    //endregion
 +
 +    //region > attachment (property)
 +    private Blob attachment;
 +    @javax.jdo.annotations.Persistent(defaultFetchGroup="false", columns = {
 +            @javax.jdo.annotations.Column(name = "attachment_name"),
 +            @javax.jdo.annotations.Column(name = "attachment_mimetype"),
 +            @javax.jdo.annotations.Column(name = "attachment_bytes", jdbcType = "BLOB", sqlType = "BLOB")
 +    })
 +    @Property(
 +            optionality = Optionality.OPTIONAL
 +    )
 +    public Blob getAttachment() {
 +        return attachment;
 +    }
 +
 +    public void setAttachment(final Blob attachment) {
 +        this.attachment = attachment;
 +    }
 +    //endregion
 +
 +    //region > doc (property)
 +    private Clob doc;
 +    @javax.jdo.annotations.Persistent(defaultFetchGroup="false", columns = {
 +            @javax.jdo.annotations.Column(name = "doc_name"),
 +            @javax.jdo.annotations.Column(name = "doc_mimetype"),
 +            @javax.jdo.annotations.Column(name = "doc_chars", jdbcType = "CLOB", sqlType = "CLOB")
 +    })
 +    @Property(
 +            optionality = Optionality.OPTIONAL
 +    )
 +    public Clob getDoc() {
 +        return doc;
 +    }
 +
 +    public void setDoc(final Clob doc) {
 +        this.doc = doc;
 +    }
 +    //endregion
 +
 +    //region > version (derived property)
 +    public Long getVersionSequence() {
-         if(!(this instanceof javax.jdo.spi.PersistenceCapable)) {
++        if(!(this instanceof Persistable)) {
 +            return null;
 +        }
-         return (Long) JDOHelper.getVersion((javax.jdo.spi.PersistenceCapable) this);
++        Persistable persistenceCapable = (Persistable) this;
++        final Long version = (Long) JDOHelper.getVersion(persistenceCapable);
++        return version;
 +    }
 +    // hide property (imperatively, based on state of object)
 +    public boolean hideVersionSequence() {
-         return !(this instanceof javax.jdo.spi.PersistenceCapable);
++        return !(this instanceof Persistable);
 +    }
 +    //endregion
 +
 +    //region > dependencies (property), add (action), remove (action)
 +
 +    // overrides the natural ordering
 +    public static class DependenciesComparator implements Comparator<ToDoItem> {
 +        @Override
 +        public int compare(final ToDoItem p, final ToDoItem q) {
 +            final Ordering<ToDoItem> byDescription = new Ordering<ToDoItem>() {
 +                public int compare(final ToDoItem p, final ToDoItem q) {
 +                    return Ordering.natural().nullsFirst().compare(p.getDescription(), q.getDescription());
 +                }
 +            };
 +            return byDescription
 +                    .compound(Ordering.<ToDoItem>natural())
 +                    .compare(p, q);
 +        }
 +    }
 +
 +    @javax.jdo.annotations.Persistent(table="ToDoItemDependencies")
 +    @javax.jdo.annotations.Join(column="dependingId")
 +    @javax.jdo.annotations.Element(column="dependentId")
 +
 +    private Set<ToDoItem> dependencies = new TreeSet<>();
 +    //private SortedSet<ToDoItem> dependencies = new TreeSet<>();  // not compatible with neo4j (as of DN v3.2.3)
 +
 +    @Collection()
 +    @CollectionLayout(/*sortedBy = DependenciesComparator.class*/) // not compatible with neo4j (as of DN v3.2.3)
 +    public Set<ToDoItem> getDependencies() {
 +        return dependencies;
 +    }
 +
 +    public void setDependencies(final Set<ToDoItem> dependencies) {
 +        this.dependencies = dependencies;
 +    }
 +
 +    public void addToDependencies(final ToDoItem toDoItem) {
 +        getDependencies().add(toDoItem);
 +    }
 +    public void removeFromDependencies(final ToDoItem toDoItem) {
 +        getDependencies().remove(toDoItem);
 +    }
 +
 +    public ToDoItem add(
 +            @ParameterLayout(typicalLength = 20)
 +            final ToDoItem toDoItem) {
 +        // By wrapping the call, Isis will detect that the collection is modified
 +        // and it will automatically send CollectionInteractionEvents to the Event Bus.
 +        // ToDoItemSubscriptions is a demo subscriber to this event
 +        wrapperFactory.wrapSkipRules(this).addToDependencies(toDoItem);
 +        return this;
 +    }
 +    public List<ToDoItem> autoComplete0Add(
 +            @Parameter(minLength = 2)
 +            final String search) {
 +        final List<ToDoItem> list = toDoItems.autoComplete(search);
 +        list.removeAll(getDependencies());
 +        list.remove(this);
 +        return list;
 +    }
 +
 +    public String disableAdd(final ToDoItem toDoItem) {
 +        if(isComplete()) {
 +            return "Cannot add dependencies for items that are complete";
 +        }
 +        return null;
 +    }
 +    // validate the provided argument prior to invoking action
 +    public String validateAdd(final ToDoItem toDoItem) {
 +        if(getDependencies().contains(toDoItem)) {
 +            return "Already a dependency";
 +        }
 +        if(toDoItem == this) {
 +            return "Can't set up a dependency to self";
 +        }
 +        return null;
 +    }
 +
 +    public ToDoItem remove(
 +            @ParameterLayout(typicalLength = 20)
 +            final ToDoItem toDoItem) {
 +        // By wrapping the call, Isis will detect that the collection is modified
 +        // and it will automatically send a CollectionInteractionEvent to the Event Bus.
 +        // ToDoItemSubscriptions is a demo subscriber to this event
 +        wrapperFactory.wrapSkipRules(this).removeFromDependencies(toDoItem);
 +        return this;
 +    }
 +    // disable action dependent on state of object
 +    public String disableRemove(final ToDoItem toDoItem) {
 +        if(isComplete()) {
 +            return "Cannot remove dependencies for items that are complete";
 +        }
 +        return getDependencies().isEmpty()? "No dependencies to remove": null;
 +    }
 +    // validate the provided argument prior to invoking action
 +    public String validateRemove(final ToDoItem toDoItem) {
 +        if(!getDependencies().contains(toDoItem)) {
 +            return "Not a dependency";
 +        }
 +        return null;
 +    }
 +    // provide a drop-down
 +    public java.util.Collection<ToDoItem> choices0Remove() {
 +        return getDependencies();
 +    }
 +    //endregion
 +
 +    //region > clone (action)
 +
 +    // the name of the action in the UI
 +    // nb: method is not called "clone()" is inherited by java.lang.Object and
 +    // (a) has different semantics and (b) is in any case automatically ignored
 +    // by the framework
 +    public ToDoItem duplicate(
 +            @Parameter(regexPattern = "\\w[@&:\\-\\,\\.\\+ \\w]*" )
 +            @ParameterLayout(named="Description")
 +            final String description,
 +            @ParameterLayout(named="Category")
 +            final Category category,
 +            @ParameterLayout(named="Subcategory")
 +            final Subcategory subcategory,
 +            @Parameter(optionality = Optionality.OPTIONAL)
 +            @ParameterLayout(named="Due by")
 +            final LocalDate dueBy,
 +            @Parameter(optionality = Optionality.OPTIONAL)
 +            @ParameterLayout(named="Cost")
 +            final BigDecimal cost) {
 +        return toDoItems.newToDo(description, category, subcategory, dueBy, cost);
 +    }
 +    public String default0Duplicate() {
 +        return getDescription() + " - Copy";
 +    }
 +    public Category default1Duplicate() {
 +        return getCategory();
 +    }
 +    public Subcategory default2Duplicate() {
 +        return getSubcategory();
 +    }
 +    public LocalDate default3Duplicate() {
 +        return getDueBy();
 +    }
 +    public List<Subcategory> choices2Duplicate(
 +            final String description, final Category category) {
 +        return toDoItems.choices2NewToDo(description, category);
 +    }
 +    public String validateDuplicate(
 +            final String description, 
 +            final Category category, final Subcategory subcategory, 
 +            final LocalDate dueBy, final BigDecimal cost) {
 +        return toDoItems.validateNewToDo(description, category, subcategory, dueBy, cost);
 +    }
 +    //endregion
 +
 +    //region > delete (action)
 +    @Action(
 +            domainEvent =DeletedEvent.class,
 +            invokeOn = InvokeOn.OBJECT_AND_COLLECTION
 +    )
 +    public List<ToDoItem> delete() {
 +        
 +        container.removeIfNotAlready(this);
 +
 +        container.informUser("Deleted " + container.titleOf(this));
 +        
 +        // invalid to return 'this' (cannot render a deleted object)
 +        return toDoItems.notYetComplete();
 +    }
 +    //endregion
 +
 +    //region > totalCost (property)
 +    @Action(
 +            semantics = SemanticsOf.SAFE,
 +            invokeOn = InvokeOn.COLLECTION_ONLY
 +    )
 +    public BigDecimal totalCost() {
 +        BigDecimal total = (BigDecimal) scratchpad.get("runningTotal");
 +        if(getCost() != null) {
 +            total = total != null ? total.add(getCost()) : getCost();
 +            scratchpad.put("runningTotal", total);
 +        }
 +        return total.setScale(2, BigDecimal.ROUND_HALF_EVEN);
 +    }
 +    //endregion
 +
 +    //region > openSourceCodeOnGithub (action)
 +    @Action(
 +            semantics = SemanticsOf.SAFE,
 +            restrictTo = RestrictTo.PROTOTYPING
 +    )
 +    public URL openSourceCodeOnGithub() throws MalformedURLException {
 +        return new URL("https://github.com/apache/isis/tree/master/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItem.java");
 +    }
 +    //endregion
 +
 +    //region > demoException (action)
 +
 +    static enum DemoExceptionType {
 +        RecoverableException,
 +        RecoverableExceptionAutoEscalated,
 +        NonRecoverableException;
 +    }
 +
 +    @Action(
 +            semantics = SemanticsOf.SAFE,
 +            restrictTo = RestrictTo.PROTOTYPING
 +    )
 +    public void demoException(
 +            @ParameterLayout(named="Type")
 +            final DemoExceptionType type) {
 +        switch(type) {
 +        case NonRecoverableException:
 +            throw new NonRecoverableException("Demo throwing " + type.name());
 +        case RecoverableException:
 +            throw new RecoverableException("Demo throwing " + type.name());
 +        case RecoverableExceptionAutoEscalated:
 +            try {
 +                // this will trigger an exception (because category cannot be null), causing the xactn to be aborted
 +                setCategory(null);
 +                container.flush();
 +            } catch(Exception e) {
 +                // it's a programming mistake to throw only a recoverable exception here, because of the xactn's state.
 +                // the framework should instead auto-escalate this to a non-recoverable exception
 +                throw new RecoverableException("Demo throwing " + type.name(), e);
 +            }
 +        }
 +    }
 +    //endregion
 +
 +    //region > lifecycle callbacks
 +
 +    public void created() {
 +        LOG.debug("lifecycle callback: created: " + this.toString());
 +    }
 +    public void loaded() {
 +        LOG.debug("lifecycle callback: loaded: " + this.toString());
 +    }
 +    public void persisting() {
 +        LOG.debug("lifecycle callback: persisting: " + this.toString());
 +    }
 +    public void persisted() {
 +        LOG.debug("lifecycle callback: persisted: " + this.toString());
 +    }
 +    public void updating() {
 +        LOG.debug("lifecycle callback: updating: " + this.toString());
 +    }
 +    public void updated() {
 +        LOG.debug("lifecycle callback: updated: " + this.toString());
 +    }
 +    public void removing() {
 +        LOG.debug("lifecycle callback: removing: " + this.toString());
 +    }
 +    public void removed() {
 +        LOG.debug("lifecycle callback: removed: " + this.toString());
 +    }
 +    //endregion
 +
 +    //region > object-level validation
 +
 +    /**
 +     * Prevent user from viewing another user's data.
 +     */
 +    public boolean hidden() {
 +        // uncomment to enable.  As things stand, the disabled() method below instead will make object "read-only".
 +        //return !Objects.equal(getOwnedBy(), container.getUser().getName());
 +        return false;
 +    }
 +
 +    /**
 +     * Prevent user from modifying any other user's data.
 +     */
 +    public String disabled(final Identifier.Type identifierType){
 +        final UserMemento currentUser = container.getUser();
 +        final String currentUserName = currentUser.getName();
 +        if(Objects.equal(getOwnedBy(), currentUserName)) { return null; }
 +        return "This object is owned by '" + getOwnedBy() + "' and cannot be modified by you";
 +    }
 +
 +    /**
 +     * In a real app, if this were actually a rule, then we'd expect that
 +     * invoking the {@link #completed() done} action would clear the {@link #getDueBy() dueBy}
 +     * property (rather than require the user to have to clear manually).
 +     */
 +    public String validate() {
 +        if(isComplete() && getDueBy() != null) {
 +            return "Due by date must be set to null if item has been completed";
 +        }
 +        return null;
 +    }
 +
 +
 +    //endregion
 +
 +
 +    //region > programmatic helpers
 +    @Programmatic // excluded from the framework's metamodel
 +    public boolean isDue() {
 +        if (getDueBy() == null) {
 +            return false;
 +        }
 +        return !toDoItems.isMoreThanOneWeekInPast(getDueBy());
 +    }
 +    //endregion
 +
 +    //region > events
 +
 +    public static abstract class AbstractActionDomainEvent extends ActionDomainEvent<ToDoItem> {
 +        private static final long serialVersionUID = 1L;
 +        private final String description;
 +        public AbstractActionDomainEvent(
 +                final String description,
 +                final ToDoItem source,
 +                final Identifier identifier,
 +                final Object... arguments) {
 +            super(source, identifier, arguments);
 +            this.description = description;
 +        }
 +        public String getEventDescription() {
 +            return description;
 +        }
 +    }
 +
 +    public static class CompletedEvent extends AbstractActionDomainEvent {
 +        private static final long serialVersionUID = 1L;
 +        public CompletedEvent(
 +                final ToDoItem source, 
 +                final Identifier identifier, 
 +                final Object... arguments) {
 +            super("completed", source, identifier, arguments);
 +        }
 +    }
 +
 +    public static class NoLongerCompletedEvent extends AbstractActionDomainEvent {
 +        private static final long serialVersionUID = 1L;
 +        public NoLongerCompletedEvent(
 +                final ToDoItem source, 
 +                final Identifier identifier, 
 +                final Object... arguments) {
 +            super("no longer completed", source, identifier, arguments);
 +        }
 +    }
 +
 +    public static class DeletedEvent extends AbstractActionDomainEvent {
 +        private static final long serialVersionUID = 1L;
 +        public DeletedEvent(
 +                final ToDoItem source, 
 +                final Identifier identifier, 
 +                final Object... arguments) {
 +            super("deleted", source, identifier, arguments);
 +        }
 +    }
 +
 +    //endregion
 +
 +    //region > predicates
 +
 +    public static class Predicates {
 +        
 +        public static Predicate<ToDoItem> thoseOwnedBy(final String currentUser) {
 +            return new Predicate<ToDoItem>() {
 +                @Override
 +                public boolean apply(final ToDoItem toDoItem) {
 +                    return Objects.equal(toDoItem.getOwnedBy(), currentUser);
 +                }
 +            };
 +        }
 +
 +        public static Predicate<ToDoItem> thoseCompleted(
 +                final boolean completed) {
 +            return new Predicate<ToDoItem>() {
 +                @Override
 +                public boolean apply(final ToDoItem t) {
 +                    return Objects.equal(t.isComplete(), completed);
 +                }
 +            };
 +        }
 +
 +        public static Predicate<ToDoItem> thoseWithSimilarDescription(final String description) {
 +            return new Predicate<ToDoItem>() {
 +                @Override
 +                public boolean apply(final ToDoItem t) {
 +                    return t.getDescription().contains(description);
 +                }
 +            };
 +        }
 +
 +        @SuppressWarnings("unchecked")
 +        public static Predicate<ToDoItem> thoseSimilarTo(final ToDoItem toDoItem) {
 +            return com.google.common.base.Predicates.and(
 +                    thoseNot(toDoItem),
 +                    thoseOwnedBy(toDoItem.getOwnedBy()),
 +                    thoseCategorised(toDoItem.getCategory()));
 +        }
 +
 +        public static Predicate<ToDoItem> thoseNot(final ToDoItem toDoItem) {
 +            return new Predicate<ToDoItem>() {
 +                @Override
 +                public boolean apply(final ToDoItem t) {
 +                    return t != toDoItem;
 +                }
 +            };
 +        }
 +
 +        public static Predicate<ToDoItem> thoseCategorised(final Category category) {
 +            return new Predicate<ToDoItem>() {
 +                @Override
 +                public boolean apply(final ToDoItem toDoItem) {
 +                    return Objects.equal(toDoItem.getCategory(), category);
 +                }
 +            };
 +        }
 +
 +        public static Predicate<ToDoItem> thoseSubcategorised(
 +                final Subcategory subcategory) {
 +            return new Predicate<ToDoItem>() {
 +                @Override
 +                public boolean apply(final ToDoItem t) {
 +                    return Objects.equal(t.getSubcategory(), subcategory);
 +                }
 +            };
 +        }
 +
 +        public static Predicate<ToDoItem> thoseCategorised(
 +                final Category category, final Subcategory subcategory) {
 +            return com.google.common.base.Predicates.and(
 +                    thoseCategorised(category), 
 +                    thoseSubcategorised(subcategory)); 
 +        }
 +
 +    }
 +
 +    //endregion
 +
 +    //region > toString, compareTo
 +    @Override
 +    public String toString() {
 +        return ObjectContracts.toString(this, "description,complete,dueBy,ownedBy");
 +    }
 +
 +    /**
 +     * Required so can store in {@link SortedSet sorted set}s (eg {@link #getDependencies()}). 
 +     */
 +    @Override
 +    public int compareTo(final ToDoItem other) {
 +        return ObjectContracts.compare(this, other, "complete,dueBy,description");
 +    }
 +    //endregion
 +
 +    //region > injected services
 +    @javax.inject.Inject
 +    DomainObjectContainer container;
 +
 +    @javax.inject.Inject
 +    ToDoItems toDoItems;
 +
 +    @javax.inject.Inject
 +    Scratchpad scratchpad;
 +
 +    /**
 +     * public only so can be injected from integ tests
 +     */
 +    @javax.inject.Inject
 +    public ActionInvocationContext actionInvocationContext;
 +
 +    /**
 +     * public only so can be injected from integ tests
 +     */
 +    @javax.inject.Inject
 +    public EventBusService eventBusService;
 +
 +    @javax.inject.Inject
 +    WrapperFactory wrapperFactory;
 +
 +    //endregion
 +
 +}