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 2017/02/14 15:13:06 UTC

[16/20] isis git commit: ISIS-1584: adds validator to prohibit contributed services (nature=VIEW or nature=VIEW_CONTRIBUTIONS_ONLY)

ISIS-1584: adds validator to prohibit contributed services (nature=VIEW or nature=VIEW_CONTRIBUTIONS_ONLY)

Disabled by default, can enable using a new configuration property.

Also:
- improved validation messages, indicating the config property that controls them
- changed existing mixins to use @Mixin(method="exec") so easier to read
- refactored the two contributed domain services (BookmarkHolder{Action/Association}Contributions) into mixins
- minor fix to simpleapp to remove its contributed domain service also (HomePageService).


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

Branch: refs/heads/master
Commit: b3e9ba5285b75fc294e27779d258bfcc08b43c08
Parents: 1d8ac13
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Mon Feb 13 23:46:32 2017 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Mon Feb 13 23:46:32 2017 +0000

----------------------------------------------------------------------
 .../guides/_rgcfg_configuring-core.adoc         |  10 ++
 .../BookmarkHolderActionContributions.java      | 106 ------------------
 .../BookmarkHolderAssociationContributions.java | 105 ------------------
 .../bookmark/BookmarkHolder_lookup.java         |  57 ++++++++++
 .../bookmark/BookmarkHolder_object.java         |  54 +++++++++
 .../MethodPrefixBasedFacetFactoryAbstract.java  |   4 +-
 .../DomainServiceFacetAnnotationFactory.java    | 109 ++++++++++++-------
 ...tSpecIdFacetDerivedFromClassNameFactory.java |   4 +-
 .../Persistable_datanucleusIdLong.java          |   8 +-
 .../Persistable_datanucleusVersionLong.java     |   8 +-
 ...Persistable_datanucleusVersionTimestamp.java |   8 +-
 .../Persistable_downloadJdoMetadata.java        |   6 +-
 .../query/JdoQueryAnnotationFacetFactory.java   |   8 +-
 .../services/homepage/HomePageService.java      |   2 +-
 .../application/manifest/isis.properties        |   7 ++
 .../tests/SimpleObject_IntegTest.java           |   4 +-
 16 files changed, 226 insertions(+), 274 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc
index cd820f0..6217f4c 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc
@@ -531,6 +531,16 @@ This configuration property can be used to enforce a rule that the object type m
 Note that although JDOQL syntax supports multiple `VARIABLES` classes, currently the validator only checks the first class name found.
 
 |`isis.reflector.validator.` +
+`mixinsOnly`
+|`true`,`false` +
+(`false`)
+| (`1.14.0-SNAPSHOT`) Mixins provide a simpler programming model to contributed domain services. +
+
+If enabled, this configuration property will treat any contributed service as invalid.
+This is by way of possibly deprecating and eventually moving contributed services from the Apache Isis programming model.
+
+
+|`isis.reflector.validator.` +
 `noParamsOnly`
 |`true`,`false` +
 (`false`)

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderActionContributions.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderActionContributions.java b/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderActionContributions.java
deleted file mode 100644
index e37275c..0000000
--- a/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderActionContributions.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- *  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 org.apache.isis.applib.services.bookmark;
-
-import java.util.List;
-import java.util.Map;
-import javax.annotation.PostConstruct;
-import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.IsisApplibModule;
-import org.apache.isis.applib.annotation.Action;
-import org.apache.isis.applib.annotation.ActionLayout;
-import org.apache.isis.applib.annotation.Contributed;
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.NatureOfService;
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.annotation.SemanticsOf;
-
-/**
- * Domain service that contributes an action (named '<tt>lookup</tt>') to
- * any class that implements {@link org.apache.isis.applib.services.bookmark.BookmarkHolder}.
- *
- * <p>
- *     This service is automatically registered.  However, if not required then its contributions can be hidden using
- *     a subscribed on its domain events.
- * </p>
- *
- * @see org.apache.isis.applib.services.bookmark.BookmarkHolderAssociationContributions
- */
-@DomainService(
-        nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY
-)
-public class BookmarkHolderActionContributions  {
-
-    public static abstract class ActionDomainEvent extends IsisApplibModule.ActionDomainEvent<BookmarkHolderActionContributions> {
-        public ActionDomainEvent(final BookmarkHolderActionContributions source, final Identifier identifier) {
-            super(source, identifier);
-        }
-
-        public ActionDomainEvent(final BookmarkHolderActionContributions source, final Identifier identifier, final Object... arguments) {
-            super(source, identifier, arguments);
-        }
-
-        public ActionDomainEvent(final BookmarkHolderActionContributions source, final Identifier identifier, final List<Object> arguments) {
-            super(source, identifier, arguments);
-        }
-    }
-
-    //region > init
-    @Programmatic
-    @PostConstruct
-    public void init(Map<String,String> props) {
-        ensureDependenciesInjected();
-    }
-
-    private void ensureDependenciesInjected() {
-        if(this.bookmarkService == null){
-            throw new IllegalStateException("BookmarkService domain service must be configured");
-        }
-    }
-    //endregion
-
-    // //////////////////////////////////////
-
-    public static class LookupDomainEvent extends ActionDomainEvent {
-        public LookupDomainEvent(final BookmarkHolderActionContributions source, final Identifier identifier, final Object... arguments) {
-            super(source, identifier, arguments);
-        }
-    }
-
-
-    @Action(
-            domainEvent = LookupDomainEvent.class,
-            semantics = SemanticsOf.SAFE
-    )
-    @ActionLayout(
-            contributed = Contributed.AS_ACTION,
-            cssClassFa = "fa-bookmark"
-    )
-    public Object lookup(final BookmarkHolder bookmarkHolder) {
-        return bookmarkService.lookup(bookmarkHolder);
-    }
-
-
-    // //////////////////////////////////////
-
-
-    @javax.inject.Inject
-    private BookmarkService bookmarkService;
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderAssociationContributions.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderAssociationContributions.java b/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderAssociationContributions.java
deleted file mode 100644
index c14a665..0000000
--- a/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolderAssociationContributions.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- *  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 org.apache.isis.applib.services.bookmark;
-
-import java.util.Map;
-import javax.annotation.PostConstruct;
-import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.IsisApplibModule;
-import org.apache.isis.applib.annotation.Action;
-import org.apache.isis.applib.annotation.ActionLayout;
-import org.apache.isis.applib.annotation.Contributed;
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.NatureOfService;
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.annotation.Property;
-import org.apache.isis.applib.annotation.SemanticsOf;
-
-/**
- * Domain service that contributes a property (named '<tt>Object</tt>') to
- * any class that implements {@link org.apache.isis.applib.services.bookmark.BookmarkHolder}.
- *
- *
- * <p>
- *     This service is automatically registered.  However, if not required then its contributions can be hidden using
- *     a subscribed on its domain events.
- * </p>
- *
- * @see org.apache.isis.applib.services.bookmark.BookmarkHolderActionContributions
- */
-@DomainService(
-        nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY
-)
-public class BookmarkHolderAssociationContributions {
-
-    public static abstract class PropertyDomainEvent<T> extends IsisApplibModule.PropertyDomainEvent<BookmarkHolderAssociationContributions, T> {
-        public PropertyDomainEvent(final BookmarkHolderAssociationContributions source, final Identifier identifier) {
-            super(source, identifier);
-        }
-
-        public PropertyDomainEvent(final BookmarkHolderAssociationContributions source, final Identifier identifier, final T oldValue, final T newValue) {
-            super(source, identifier, oldValue, newValue);
-        }
-    }
-
-    // //////////////////////////////////////
-
-    //region > init
-    @Programmatic
-    @PostConstruct
-    public void init(Map<String,String> props) {
-        ensureDependenciesInjected();
-    }
-
-    private void ensureDependenciesInjected() {
-        if(this.bookmarkService == null){
-            throw new IllegalStateException("BookmarkService domain service must be configured");
-        }
-    }
-    //endregion
-
-    // //////////////////////////////////////
-
-    public static class ObjectDomainEvent extends PropertyDomainEvent<Object> {
-        public ObjectDomainEvent(final BookmarkHolderAssociationContributions source, final Identifier identifier) {
-            super(source, identifier);
-        }
-
-        public ObjectDomainEvent(final BookmarkHolderAssociationContributions source, final Identifier identifier, final Object oldValue, final Object newValue) {
-            super(source, identifier, oldValue, newValue);
-        }
-    }
-
-    @Action(semantics = SemanticsOf.SAFE)
-    @ActionLayout(
-        contributed = Contributed.AS_ASSOCIATION
-    )
-    @Property(
-        domainEvent = ObjectDomainEvent.class
-    )
-    public Object object(final BookmarkHolder bookmarkHolder) {
-        return bookmarkService.lookup(bookmarkHolder);
-    }
-
-    // //////////////////////////////////////
-
-    @javax.inject.Inject
-    private BookmarkService bookmarkService;
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_lookup.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_lookup.java b/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_lookup.java
new file mode 100644
index 0000000..cffd71e
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_lookup.java
@@ -0,0 +1,57 @@
+/*
+ *  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 org.apache.isis.applib.services.bookmark;
+
+import org.apache.isis.applib.IsisApplibModule;
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.Contributed;
+import org.apache.isis.applib.annotation.Mixin;
+import org.apache.isis.applib.annotation.SemanticsOf;
+
+@Mixin(method = "exec")
+public class BookmarkHolder_lookup {
+
+    public static class ActionDomainEvent extends IsisApplibModule.ActionDomainEvent<BookmarkHolder_lookup> {}
+
+    private final BookmarkHolder bookmarkHolder;
+
+    public BookmarkHolder_lookup(final BookmarkHolder bookmarkHolder) {
+        this.bookmarkHolder = bookmarkHolder;
+    }
+
+
+    @Action(
+            domainEvent = ActionDomainEvent.class,
+            semantics = SemanticsOf.SAFE
+    )
+    @ActionLayout(
+            contributed = Contributed.AS_ACTION,
+            cssClassFa = "fa-bookmark"
+    )
+    public Object exec() {
+        return bookmarkService.lookup(bookmarkHolder);
+    }
+
+
+
+    @javax.inject.Inject
+    private BookmarkService bookmarkService;
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_object.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_object.java b/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_object.java
new file mode 100644
index 0000000..be7e87d
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkHolder_object.java
@@ -0,0 +1,54 @@
+/*
+ *  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 org.apache.isis.applib.services.bookmark;
+
+import org.apache.isis.applib.IsisApplibModule;
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.Contributed;
+import org.apache.isis.applib.annotation.Mixin;
+import org.apache.isis.applib.annotation.SemanticsOf;
+
+@Mixin(method = "exec")
+public class BookmarkHolder_object {
+
+    private final BookmarkHolder bookmarkHolder;
+
+    public BookmarkHolder_object(final BookmarkHolder bookmarkHolder) {
+        this.bookmarkHolder = bookmarkHolder;
+    }
+
+    public static class ActionDomainEvent extends IsisApplibModule.ActionDomainEvent<BookmarkHolder_object> {}
+
+    @Action(
+            semantics = SemanticsOf.SAFE,
+            domainEvent = BookmarkHolder_object.ActionDomainEvent.class
+    )
+    @ActionLayout(
+        contributed = Contributed.AS_ASSOCIATION
+    )
+    public Object exec() {
+        return bookmarkService.lookup(bookmarkHolder);
+    }
+
+
+    @javax.inject.Inject
+    private BookmarkService bookmarkService;
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/MethodPrefixBasedFacetFactoryAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/MethodPrefixBasedFacetFactoryAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/MethodPrefixBasedFacetFactoryAbstract.java
index 6bfe2b5..67a20a9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/MethodPrefixBasedFacetFactoryAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/MethodPrefixBasedFacetFactoryAbstract.java
@@ -89,7 +89,9 @@ public abstract class MethodPrefixBasedFacetFactoryAbstract
                             final String explanation =
                                     objectAction.getParameterCount() > 0 && noParamsOnly &&
                                     (Objects.equals(prefix, MethodPrefixConstants.HIDE_PREFIX) || Objects.equals(prefix, MethodPrefixConstants.DISABLE_PREFIX))
-                                    ? " (note that such methods must have no parameters)"
+                                    ? " (note that such methods must have no parameters, '"
+                                            + ISIS_REFLECTOR_VALIDATOR_NO_PARAMS_ONLY_KEY
+                                            + "' config property)"
                                     : "";
 
                             String message = "%s#%s: has prefix %s, is probably intended as a supporting method for a property, collection or action%s.  If the method is intended to be an action, then rename and use @ActionLayout(named=\"...\") or ignore completely using @Programmatic";

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservice/annotation/DomainServiceFacetAnnotationFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservice/annotation/DomainServiceFacetAnnotationFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservice/annotation/DomainServiceFacetAnnotationFactory.java
index c04ec1d..cdbbce1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservice/annotation/DomainServiceFacetAnnotationFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservice/annotation/DomainServiceFacetAnnotationFactory.java
@@ -26,6 +26,7 @@ import com.google.common.collect.Lists;
 
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
@@ -36,6 +37,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForValidationFailures;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting;
 import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
 
@@ -45,6 +47,12 @@ public class DomainServiceFacetAnnotationFactory extends FacetFactoryAbstract im
             "isis.reflector.validator.serviceActionsOnly";
     public static final boolean ISIS_REFLECTOR_VALIDATOR_SERVICE_ACTIONS_ONLY_DEFAULT = false;
 
+    public static final String ISIS_REFLECTOR_VALIDATOR_MIXINS_ONLY_KEY =
+            "isis.reflector.validator.mixinsOnly";
+    public static final boolean ISIS_REFLECTOR_VALIDATOR_MIXINS_ONLY_DEFAULT = false;
+
+    private MetaModelValidatorForValidationFailures mixinOnlyValidator = new MetaModelValidatorForValidationFailures();
+
     public DomainServiceFacetAnnotationFactory() {
         super(FeatureType.OBJECTS_ONLY);
     }
@@ -56,65 +64,90 @@ public class DomainServiceFacetAnnotationFactory extends FacetFactoryAbstract im
         if (annotation == null) {
             return;
         }
-        FacetUtil.addFacet(
-                new DomainServiceFacetAnnotation(
-                        processClassContext.getFacetHolder(),
-                        annotation.repositoryFor(), annotation.nature()));
+        FacetHolder facetHolder = processClassContext.getFacetHolder();
+        DomainServiceFacet domainServiceFacet = new DomainServiceFacetAnnotation(
+                facetHolder,
+                annotation.repositoryFor(), annotation.nature());
+        FacetUtil.addFacet(domainServiceFacet);
+
+
         FacetUtil.addFacet(
                 new IconFacetDerivedFromDomainServiceAnnotation(
-                        processClassContext.getFacetHolder(),
+                        facetHolder,
                         annotation.repositoryFor()));
+
+
+        // the mixinOnlyValidator is only added if the config property is set.
+        switch (domainServiceFacet.getNatureOfService()) {
+        case VIEW:
+            mixinOnlyValidator.addFailure(
+                    "%s: menu/contributed services (nature == VIEW) are prohibited ('%s' config property); convert into a mixin (@Mixin annotation) instead",
+                    cls.getName(), ISIS_REFLECTOR_VALIDATOR_MIXINS_ONLY_KEY);
+            break;
+        case VIEW_CONTRIBUTIONS_ONLY:
+            mixinOnlyValidator.addFailure(
+                    "%s: contributed services (nature == VIEW_CONTRIBUTIONS_ONLY) are prohibited ('%s' config property); convert into a mixin (@Mixin annotation) instead",
+                    cls.getName(), ISIS_REFLECTOR_VALIDATOR_MIXINS_ONLY_KEY);
+            break;
+        }
     }
 
 
     @Override
     public void refineMetaModelValidator(final MetaModelValidatorComposite metaModelValidator, final IsisConfiguration configuration) {
 
-        boolean serviceActionsOnly = configuration.getBoolean(
+        final boolean serviceActionsOnly = configuration.getBoolean(
                 ISIS_REFLECTOR_VALIDATOR_SERVICE_ACTIONS_ONLY_KEY,
                 ISIS_REFLECTOR_VALIDATOR_SERVICE_ACTIONS_ONLY_DEFAULT);
-        if(!serviceActionsOnly) {
-            return;
-        }
+        if (serviceActionsOnly) {
+            metaModelValidator.add(new MetaModelValidatorVisiting(new MetaModelValidatorVisiting.Visitor() {
 
-        metaModelValidator.add(new MetaModelValidatorVisiting(new MetaModelValidatorVisiting.Visitor() {
+                @Override
+                public boolean visit(final ObjectSpecification thisSpec, final ValidationFailures validationFailures) {
+                    validate(thisSpec, validationFailures);
+                    return true;
+                }
 
-            @Override
-            public boolean visit(final ObjectSpecification thisSpec, final ValidationFailures validationFailures) {
-                validate(thisSpec, validationFailures);
-                return true;
-            }
+                private void validate(
+                        final ObjectSpecification thisSpec,
+                        final ValidationFailures validationFailures) {
 
-            private void validate(
-                    final ObjectSpecification thisSpec,
-                    final ValidationFailures validationFailures) {
+                    if(!thisSpec.containsFacet(DomainServiceFacet.class)) {
+                        return;
+                    }
 
-                if(!thisSpec.containsFacet(DomainServiceFacet.class)) {
-                    return;
-                }
+                    final List<String> associationNames = Lists.newArrayList();
 
-                final List<String> associationNames = Lists.newArrayList();
+                    final List<ObjectAssociation> associations = thisSpec.getAssociations(Contributed.EXCLUDED);
+                    for (ObjectAssociation association: associations) {
+                        final String associationName = association.getName();
+                        // it's okay to have an "association" called "Id" (corresponding to getId() method)
+                        if("Id".equalsIgnoreCase(associationName)) {
+                            continue;
+                        }
+                        associationNames.add(associationName);
+                    }
 
-                final List<ObjectAssociation> associations = thisSpec.getAssociations(Contributed.EXCLUDED);
-                for (ObjectAssociation association: associations) {
-                    final String associationName = association.getName();
-                    // it's okay to have an "association" called "Id" (corresponding to getId() method)
-                    if("Id".equalsIgnoreCase(associationName)) {
-                        continue;
+                    if(associationNames.isEmpty()) {
+                        return;
                     }
-                    associationNames.add(associationName);
-                }
 
-                if(associationNames.isEmpty()) {
-                    return;
+                    validationFailures.add(
+                            "%s: services can only have actions ('%s' config property), not properties or collections; annotate with @Programmatic if required.  Found: %s",
+                            thisSpec.getFullIdentifier(),
+                            ISIS_REFLECTOR_VALIDATOR_SERVICE_ACTIONS_ONLY_KEY,
+                            Joiner.on(", ").join(associationNames));
                 }
+            }));
+        }
+
+        boolean mixinsOnly = configuration.getBoolean(
+                ISIS_REFLECTOR_VALIDATOR_MIXINS_ONLY_KEY,
+                ISIS_REFLECTOR_VALIDATOR_MIXINS_ONLY_DEFAULT);
+        if (mixinsOnly) {
+            metaModelValidator.add(mixinOnlyValidator);
+        }
 
-                validationFailures.add(
-                        "%s: services can only have actions, not properties or collections (annotate with @Programmatic if required).  Found: %s",
-                        thisSpec.getFullIdentifier(),
-                        Joiner.on(", ").join(associationNames));
-            }
-        }));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
index 3d16efa..6fccfcd 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
@@ -89,8 +89,8 @@ public class ObjectSpecIdFacetDerivedFromClassNameFactory extends FacetFactoryAb
                         ObjectSpecIdFacet objectSpecIdFacet = objectSpec.getFacet(ObjectSpecIdFacet.class);
                         if(objectSpecIdFacet instanceof ObjectSpecIdFacetDerivedFromClassName) {
                             validationFailures.add(
-                                    "%s: the object type must be specified explicitly.  Defaulting the object type from the package/class/package name can lead to data migration issues for apps deployed to production (if the class is subsequently refactored).  Use @Discriminator, @DomainObject(objectType=...) or @PersistenceCapable(schema=...) to specify explicitly.",
-                                    objectSpec.getFullIdentifier());
+                                    "%s: the object type must be specified explicitly ('%s' config property).  Defaulting the object type from the package/class/package name can lead to data migration issues for apps deployed to production (if the class is subsequently refactored).  Use @Discriminator, @DomainObject(objectType=...) or @PersistenceCapable(schema=...) to specify explicitly.",
+                                    objectSpec.getFullIdentifier(), ISIS_REFLECTOR_VALIDATOR_EXPLICIT_OBJECT_TYPE_KEY);
                         }
                     }
                 });

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusIdLong.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusIdLong.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusIdLong.java
index 34bcf7e..ca0e4be 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusIdLong.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusIdLong.java
@@ -32,7 +32,7 @@ import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.annotation.Where;
 
-@Mixin
+@Mixin(method = "exec")
 public class Persistable_datanucleusIdLong {
 
     private final Persistable persistable;
@@ -55,7 +55,7 @@ public class Persistable_datanucleusIdLong {
             hidden = Where.ALL_TABLES
     )
     @MemberOrder(name = "Metadata", sequence = "800.1")
-    public Long $$() {
+    public Long exec() {
         final Object objectId = JDOHelper.getObjectId(persistable);
         if(objectId instanceof DatastoreId) {
             final DatastoreId datastoreId = (DatastoreId) objectId;
@@ -65,8 +65,8 @@ public class Persistable_datanucleusIdLong {
         return null;
     }
 
-    public boolean hide$$() {
-        return $$() == null;
+    public boolean hideExec() {
+        return exec() == null;
     }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionLong.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionLong.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionLong.java
index d52a8d4..5fd83c4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionLong.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionLong.java
@@ -31,7 +31,7 @@ import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.annotation.Where;
 
-@Mixin
+@Mixin(method = "exec")
 public class Persistable_datanucleusVersionLong {
 
     private final Persistable persistable;
@@ -54,13 +54,13 @@ public class Persistable_datanucleusVersionLong {
             hidden = Where.ALL_TABLES
     )
     @MemberOrder(name = "Metadata", sequence = "800.2")
-    public Long $$() {
+    public Long exec() {
         final Object version = JDOHelper.getVersion(persistable);
         return version != null && version instanceof Long ? (Long) version : null;
     }
 
-    public boolean hide$$() {
-        return $$() == null;
+    public boolean hideExec() {
+        return exec() == null;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionTimestamp.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionTimestamp.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionTimestamp.java
index 86f2069..f19ac00 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionTimestamp.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_datanucleusVersionTimestamp.java
@@ -31,7 +31,7 @@ import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.annotation.Where;
 
-@Mixin
+@Mixin(method = "exec")
 public class Persistable_datanucleusVersionTimestamp {
 
     private final Persistable persistable;
@@ -54,13 +54,13 @@ public class Persistable_datanucleusVersionTimestamp {
             hidden = Where.ALL_TABLES
     )
     @MemberOrder(name = "Metadata", sequence = "800.2")
-    public java.sql.Timestamp $$() {
+    public java.sql.Timestamp exec() {
         final Object version = JDOHelper.getVersion(persistable);
         return version != null && version instanceof java.sql.Timestamp ? (java.sql.Timestamp) version : null;
     }
 
-    public boolean hide$$() {
-        return $$() == null;
+    public boolean hideExec() {
+        return exec() == null;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_downloadJdoMetadata.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_downloadJdoMetadata.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_downloadJdoMetadata.java
index 3155247..f84bbf4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_downloadJdoMetadata.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/jdosupport/Persistable_downloadJdoMetadata.java
@@ -36,7 +36,7 @@ import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.services.jdosupport.IsisJdoSupport;
 import org.apache.isis.applib.value.Clob;
 
-@Mixin
+@Mixin(method = "exec")
 public class Persistable_downloadJdoMetadata {
 
     private final Persistable persistable;
@@ -57,7 +57,7 @@ public class Persistable_downloadJdoMetadata {
             position = ActionLayout.Position.PANEL_DROPDOWN
     )
     @MemberOrder(name = "datanucleusIdLong", sequence = "710.1")
-    public Clob $$(
+    public Clob exec(
             @ParameterLayout(named = ".jdo file name")
             final String fileName) throws JAXBException, IOException {
 
@@ -70,7 +70,7 @@ public class Persistable_downloadJdoMetadata {
         return new Clob(Util.withSuffix(fileName, "jdo"), "text/xml", xml);
     }
 
-    public String default0$$() {
+    public String default0Exec() {
         return Util.withSuffix(persistable.getClass().getName(), "jdo");
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/core/metamodel/src/main/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactory.java
index 74ebc45..9f2f88e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactory.java
@@ -118,7 +118,7 @@ public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract impleme
                             final String className = objectSpec.getCorrespondingClass().getName();
                             if(!getSpecificationLoader().loaded(fromClassName)) {
                                 validationFailures.add(
-                                        "Error in JDOQL query on %s, class name for FROM clause not recognized (JDOQL : %s)",
+                                        "%s: error in JDOQL query, class name for FROM clause not recognized (JDOQL : %s)",
                                         className, query);
                                 return;
 
@@ -133,7 +133,7 @@ public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract impleme
                                 return;
                             }
                             validationFailures.add(
-                                    "Error in JDOQL query on %s, class name after FROM clause should be same as class name on which annotated, or one of its supertypes (JDOQL : %s)",
+                                    "%s: error in JDOQL query, class name after FROM clause should be same as class name on which annotated, or one of its supertypes (JDOQL : %s)",
                                     className, query);
                         }
 
@@ -182,7 +182,7 @@ public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract impleme
 
                             if(!getSpecificationLoader().loaded(variablesClassName)) {
                                 validationFailures.add(
-                                        "Error in JDOQL query on %s, class name for VARIABLES clause not recognized (JDOQL : %s)",
+                                        "%s: error in JDOQL query, class name for VARIABLES clause not recognized (JDOQL : %s)",
                                         className, query);
                                 return;
                             }
@@ -193,7 +193,7 @@ public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract impleme
 
                             if(persistenceCapableFacet == null) {
                                 validationFailures.add(
-                                        "Error in JDOQL query on %s, class name for VARIABLES clause is not annotated as @PersistenceCapable (JDOQL : %s)",
+                                        "%s: error in JDOQL query, class name for VARIABLES clause is not annotated as @PersistenceCapable (JDOQL : %s)",
                                         className, query);
                                 return;
                             }

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/example/application/simpleapp/application/src/main/java/domainapp/application/services/homepage/HomePageService.java
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/application/src/main/java/domainapp/application/services/homepage/HomePageService.java b/example/application/simpleapp/application/src/main/java/domainapp/application/services/homepage/HomePageService.java
index 71a5b30..cb073ab 100644
--- a/example/application/simpleapp/application/src/main/java/domainapp/application/services/homepage/HomePageService.java
+++ b/example/application/simpleapp/application/src/main/java/domainapp/application/services/homepage/HomePageService.java
@@ -26,7 +26,7 @@ import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.services.factory.FactoryService;
 
 @DomainService(
-        nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY // trick to suppress the actions from the top-level menu
+        nature = NatureOfService.DOMAIN // trick to suppress the actions from the top-level menu
 )
 public class HomePageService {
 

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/example/application/simpleapp/application/src/main/resources/domainapp/application/manifest/isis.properties
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/application/src/main/resources/domainapp/application/manifest/isis.properties b/example/application/simpleapp/application/src/main/resources/domainapp/application/manifest/isis.properties
index 866c0af..e76cd2b 100644
--- a/example/application/simpleapp/application/src/main/resources/domainapp/application/manifest/isis.properties
+++ b/example/application/simpleapp/application/src/main/resources/domainapp/application/manifest/isis.properties
@@ -88,6 +88,13 @@ isis.reflector.validator.serviceActionsOnly=true
 
 
 #
+# Whether to use only mixins, and no contributed services.
+# If not specified, defaults to false
+#
+isis.reflector.validator.mixinsOnly=true
+
+
+#
 # Implementation to use for reading dynamic layout.
 # Default implementation reads Xxx.layout.json files from classpath.
 #

http://git-wip-us.apache.org/repos/asf/isis/blob/b3e9ba52/example/application/simpleapp/module-simple/src/test/java/domainapp/modules/simple/integtests/tests/SimpleObject_IntegTest.java
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/module-simple/src/test/java/domainapp/modules/simple/integtests/tests/SimpleObject_IntegTest.java b/example/application/simpleapp/module-simple/src/test/java/domainapp/modules/simple/integtests/tests/SimpleObject_IntegTest.java
index f1404c0..7a95cd6 100644
--- a/example/application/simpleapp/module-simple/src/test/java/domainapp/modules/simple/integtests/tests/SimpleObject_IntegTest.java
+++ b/example/application/simpleapp/module-simple/src/test/java/domainapp/modules/simple/integtests/tests/SimpleObject_IntegTest.java
@@ -137,7 +137,7 @@ public class SimpleObject_IntegTest extends SimpleModuleIntegTestAbstract {
         @Test
         public void should_be_populated() throws Exception {
             // when
-            final Long id = mixin(Persistable_datanucleusIdLong.class, simpleObject).$$();
+            final Long id = mixin(Persistable_datanucleusIdLong.class, simpleObject).exec();
 
             // then
             assertThat(id).isGreaterThanOrEqualTo(0);
@@ -149,7 +149,7 @@ public class SimpleObject_IntegTest extends SimpleModuleIntegTestAbstract {
         @Test
         public void should_be_populated() throws Exception {
             // when
-            final Timestamp timestamp = mixin(Persistable_datanucleusVersionTimestamp.class, simpleObject).$$();
+            final Timestamp timestamp = mixin(Persistable_datanucleusVersionTimestamp.class, simpleObject).exec();
             // then
             assertThat(timestamp).isNotNull();
         }