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/07/28 22:00:42 UTC

isis git commit: ISIS-1158: adding i18n support to Specification interface.

Repository: isis
Updated Branches:
  refs/heads/master 64ebce60c -> 6dd04ac12


ISIS-1158: adding i18n support to Specification interface.

also added some documentation for specification, in the ref guide.


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

Branch: refs/heads/master
Commit: 6dd04ac1298825829273a8291e192171f33dee51
Parents: 64ebce6
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Tue Jul 28 20:59:54 2015 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Tue Jul 28 20:59:54 2015 +0100

----------------------------------------------------------------------
 ...notations_manpage-Parameter_mustSatisfy.adoc |  15 +++
 ...nnotations_manpage-Property_mustSatisfy.adoc |  13 ++-
 .../src/main/asciidoc/guides/_rg_classes.adoc   |   2 +
 .../main/asciidoc/guides/_rg_classes_i18n.adoc  |  11 ++
 ...classes_i18n_manpage-TranslatableString.adoc |  13 +++
 .../main/asciidoc/guides/_rg_classes_spec.adoc  | 108 ++++++++++++++++++
 .../asciidoc/guides/_rg_classes_utility.adoc    |   1 +
 .../asciidoc/guides/_ug_more-advanced_i18n.adoc |   2 +-
 .../applib/spec/AbstractSpecification2.java     | 111 +++++++++++++++++++
 .../apache/isis/applib/spec/Specification2.java |  38 +++++++
 .../applib/spec/AbstractSpecification2Test.java |  92 +++++++++++++++
 .../applib/spec/AbstractSpecificationTest.java  |  97 ++++++++++++++++
 .../applib/spec/SpecificationAbstractTest.java  |  97 ----------------
 ...ionFromMustSatisfyAnnotationOnTypeFacet.java |  26 +++--
 ...MustSatisfyAnnotationOnTypeFacetFactory.java |  24 +++-
 .../MustSatisfySpecificationFacetAbstract.java  |  26 +++--
 .../mustsatisfyspec/SpecificationEvaluator.java |  80 +++++++++++++
 .../ParameterAnnotationFacetFactory.java        |  15 ++-
 ...acetForMustSatisfyAnnotationOnParameter.java |  14 ++-
 ...pecificationFacetForParameterAnnotation.java |  15 ++-
 .../PropertyAnnotationFacetFactory.java         |   4 +-
 ...FacetForMustSatisfyAnnotationOnProperty.java |  15 ++-
 ...SpecificationFacetForPropertyAnnotation.java |  10 +-
 ...icationFacetFactoryProcessParameterTest.java |  17 +++
 ...ficationFacetFactoryProcessPropertyTest.java |  29 ++++-
 ...cificationValidatingInteractionMoreTest.java |  47 ++++++--
 ...ySpecificationValidatingInteractionTest.java |  47 +++++---
 27 files changed, 798 insertions(+), 171 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Parameter_mustSatisfy.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Parameter_mustSatisfy.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Parameter_mustSatisfy.adoc
index d3d6c5e..bfadfca 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Parameter_mustSatisfy.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Parameter_mustSatisfy.adoc
@@ -48,3 +48,18 @@ public class CustomerRepository {
 ----
 <1> the `AbstractSpecification` class conveniently handles type-safety and dealing with null values.  The applib also provides `SpecificationAnd` and `SpecificationOr` to allow specifications to be combined "algebraically".
 
+
+It is also possible to provide translatable reasons.  Rather than implement `Specification`, instead implement `Specification2` which defines the API:
+
+[source,java]
+----
+public interface Specification2 extends Specification {
+    public TranslatableString satisfiesTranslatable(Object obj); // <1>
+}
+----
+<1> Return `null` if specification satisfied, otherwise the reason as a translatable string
+
+With `Specification2` there is no need to implement the inherited `satifies(Object)`; that method will never be called.
+
+
+

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Property_mustSatisfy.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Property_mustSatisfy.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Property_mustSatisfy.adoc
index 4d2ad97..44f0660 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Property_mustSatisfy.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Property_mustSatisfy.adoc
@@ -32,7 +32,7 @@ public class StartWithCapitalLetterSpecification
     }
 }
 public class Customer {
-    @MustSatisfy(StartWithCapitalLetterSpecification.class)
+    @Property(mustSatisfy=StartWithCapitalLetterSpecification.class)
     public String getFirstName() { ... }
     ...
 }
@@ -40,6 +40,17 @@ public class Customer {
 <1> the `AbstractSpecification` class conveniently handles type-safety and dealing with null values.  The applib also provides `SpecificationAnd` and `SpecificationOr` to allow specifications to be combined "algebraically".
 
 
+It is also possible to provide translatable reasons.  Rather than implement `Specification`, instead implement `Specification2` which defines the API:
+
+[source,java]
+----
+public interface Specification2 extends Specification {
+    public TranslatableString satisfiesTranslatable(Object obj); // <1>
+}
+----
+<1> Return `null` if specification satisfied, otherwise the reason as a translatable string
+
+With `Specification2` there is no need to implement the inherited `satifies(Object)`; that method will never be called.
 
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_classes.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_classes.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_classes.adoc
index 1dfb797..7a93058 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rg_classes.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_classes.adoc
@@ -10,5 +10,7 @@ NOTE: TODO
 include::_rg_classes_super.adoc[leveloffset=+1]
 include::_rg_classes_value-types.adoc[leveloffset=+1]
 include::_rg_classes_utility.adoc[leveloffset=+1]
+include::_rg_classes_spec.adoc[leveloffset=+1]
+include::_rg_classes_i18n.adoc[leveloffset=+1]
 include::_rg_classes_mixins.adoc[leveloffset=+1]
 

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n.adoc
new file mode 100644
index 0000000..7e7c806
--- /dev/null
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n.adoc
@@ -0,0 +1,11 @@
+[[_rg_classes_i18n]]
+= i18n support
+:Notice: 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.
+:_basedir: ../
+:_imagesdir: images/
+
+
+The `org.apache.isis.applib.services.i18n` package contains a single class to support i18n.
+
+
+include::_rg_classes_i18n_manpage-TranslatableString.adoc[leveloffset=+1]

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n_manpage-TranslatableString.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n_manpage-TranslatableString.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n_manpage-TranslatableString.adoc
new file mode 100644
index 0000000..026edb1
--- /dev/null
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_i18n_manpage-TranslatableString.adoc
@@ -0,0 +1,13 @@
+[[_rg_classes_i18n_manpage-TranslatableString]]
+= `TranslatableString`
+:Notice: 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.
+:_basedir: ../
+:_imagesdir: images/
+
+
+IMPORTANT: TODO - see xref:ug.adoc#_ug_more-advanced_i18n[user guide, i18n].
+
+The `TranslatableString` utility class ...
+
+
+

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_classes_spec.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_classes_spec.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_spec.adoc
new file mode 100644
index 0000000..2edf9ea
--- /dev/null
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_spec.adoc
@@ -0,0 +1,108 @@
+[[_rg_classes_spec]]
+= Specification pattern
+:Notice: 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.
+:_basedir: ../
+:_imagesdir: images/
+
+
+The interfaces and classes listed in this chapter provide support for the  `Specification` pattern, as described in Eric Evans' book _Domain Driven Design_, p224.
+
+Apache Isis will automatically apply such specifications as validation rules on properties (as per xref:rg.adoc#_rg_annotations_manpage-Property_mustSatisfy[`@Property#mustSatisfy()`]) and on action parameters (as per xref:rg.adoc#_rg_annotations_manpage-Parameter_mustSatisfy[`@Parameter#mustSatisfy()`]).
+
+
+
+[_rg_classes_spec-Specification]
+== `Specification`
+
+The heart of the support for this pattern is the `Specification` interface:
+
+[source,java]
+----
+public interface Specification {
+    public String satisfies(Object obj);  // <1>
+}
+----
+<1> if returns `null`, then the constraint is satisfies; otherwise returns the reason why the constraint has not been satisfied.
+
+
+For example:
+
+[source,java]
+----
+public class StartWithCapitalLetterSpecification implements Specification {
+    public String satisfies(Object proposedObj) {
+        String proposed = (String)proposedObj;               // <1>
+        return "".equals(proposed)
+            ? "Empty string"
+            : !Character.isUpperCase(proposed.charAt(0))
+                ? "Does not start with a capital letter"
+                : null;
+    }
+}
+public class Customer {
+    @Property(mustSatisfy=StartWithCapitalLetterSpecification.class)
+    public String getFirstName() { ... }
+    ...
+}
+----
+<1> this ugly cast can be avoided using some of the other classes available; see below.
+
+
+
+[_rg_classes_spec-Specification2]
+== `Specification2`
+
+The `Specification2` interface extends the `Specification` API to add support for i18n.  This is done by defining an additional method that returns a xref:rg.adoc#_rg_classes_i18n_manpage-TranslatableString[translatable string]:
+
+[source,java]
+----
+public interface Specification2 extends Specification {
+    public TranslatableString satisfiesTranslatable(Object obj);  // <1>
+}
+----
+<1> if returns `null`, then the constraint is satisfies; otherwise returns the reason why the constraint has not been satisfied.
+
+
+Note that if implementing `Specification2` then there is no need to also provide an implementation of the inherited `satisfies(Object)` method; this will never be called by the framework for `Specification2` instances.
+
+
+
+
+[_rg_classes_spec-adapter_classes]
+== Adapter classes
+
+The `AbstractSpecification` and `AbstractSpecification2` adapter classes provide a partial implementation of the respective interfaces, providing type-safety.  (Their design is modelled on the `TypesafeMatcher` class within link:http://hamcrest.org/JavaHamcrest/[Hamcrest]).
+
+For example:
+
+[source,java]
+----
+public class StartWithCapitalLetterSpecification extends AbstractSpecification<String> {
+    public String satisfiesSafely(String proposed) {
+        return "".equals(proposed)
+            ? "Empty string"
+            : !Character.isUpperCase(proposed.charAt(0))
+                ? "Does not start with a capital letter"
+                : null;
+    }
+}
+public class Customer {
+    @Property(mustSatisfy=StartWithCapitalLetterSpecification.class)
+    public String getFirstName() { ... }
+    ...
+}
+----
+
+The `AbstractSpecification2` class is almost identical; its type-safe method is `satisfiesTranslatableSafely(T)` instead.
+
+
+[_rg_classes_spec-combining_specifications]
+== Combining specifications
+
+There are also adapter classes that can be inherited from to combine specifications:
+
+* `SpecificationAnd` - all provided specifications' constraints must be met
+* `SpecificationOr` - at least one provided specifications' constraints must be met
+* `SpecificationNot` - its constraints are met if-and-only-if the provided specification's constraint was _not_ met.
+
+Note that these adapter classes inherit `Specification` but do not inherit `Specification2`; in other words they do not support i18n.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_rg_classes_utility.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_classes_utility.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_utility.adoc
index 2f75c75..8091906 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rg_classes_utility.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_classes_utility.adoc
@@ -13,3 +13,4 @@ include::_rg_classes_utility_manpage-ObjectContracts.adoc[leveloffset=+1]
 include::_rg_classes_utility_manpage-Reasons.adoc[leveloffset=+1]
 include::_rg_classes_utility_manpage-TitleBuffer.adoc[leveloffset=+1]
 
+

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/adocs/documentation/src/main/asciidoc/guides/_ug_more-advanced_i18n.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_ug_more-advanced_i18n.adoc b/adocs/documentation/src/main/asciidoc/guides/_ug_more-advanced_i18n.adoc
index 66c08c3..e7290f8 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_ug_more-advanced_i18n.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_ug_more-advanced_i18n.adoc
@@ -7,7 +7,7 @@
 
 Apache Isis' support for i18n allows every element of the domain model (the class names, property names, action names, parameter names and so forth) to be translated.
 
-It also supports translations of messages raised imperatively, by which we mean as the result of a call to `title()` to obtain an object's title, or messages messages resulting from any business rule violations (eg `disableXxx()` or `validateXxx()`, and so on.
+It also supports translations of messages raised imperatively, by which we mean as the result of a call to `title()` to obtain an object's title, or messages resulting from any business rule violations (eg xref:rg.adoc#_rg_methods_prefixes_manpage-disable[`disable...()`] or xref:rg.adoc#_rg_methods_prefixes_manpage-validate[`validate...()`], and so on.
 
 Isis does not translate the values of your domain objects, though.  So, if you have a domain concept such as `Country` whose name is intended to be localized according to the current user, you will need to model
 

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java b/core/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java
new file mode 100644
index 0000000..0cb7b8b
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java
@@ -0,0 +1,111 @@
+/*
+ *  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.spec;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.applib.services.i18n.TranslatableString;
+
+/**
+ * Adapter to make it easy to write {@link Specification}s.
+ * 
+ * <p>
+ * Provides two main features:
+ * <ul>
+ * <li>first, is type-safe (with invalid type being either ignored or
+ * constituting a failure), and
+ * <li>second, checks for nulls (with a null either being ignore or again
+ * constituting a failure)
+ * </ul>
+ * 
+ * <p>
+ * Implementation note: inspired by (borrowed code from) Hamcrest's
+ * <tt>TypeSafeMatcher</tt>.
+ */
+public abstract class AbstractSpecification2<T> implements Specification2 {
+
+    public enum TypeChecking {
+        ENSURE_CORRECT_TYPE, IGNORE_INCORRECT_TYPE,
+    }
+
+    public enum Nullability {
+        ENSURE_NOT_NULL, IGNORE_IF_NULL
+    }
+
+    private static Class<?> findExpectedType(final Class<?> fromClass) {
+        for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) {
+            for (final Method method : c.getDeclaredMethods()) {
+                if (isSatisfiesTranslatableSafelyMethod(method)) {
+                    return method.getParameterTypes()[0];
+                }
+            }
+        }
+
+        throw new Error("Cannot determine correct type for satisfiesSafely() method.");
+    }
+
+    private static boolean isSatisfiesTranslatableSafelyMethod(final Method method) {
+        return method.getName().equals("satisfiesTranslatableSafely") && method.getParameterTypes().length == 1 && !method.isSynthetic();
+    }
+
+    private final Class<?> expectedType;
+    private final Nullability nullability;
+    private final TypeChecking typeChecking;
+
+    protected AbstractSpecification2() {
+        this(Nullability.IGNORE_IF_NULL, TypeChecking.IGNORE_INCORRECT_TYPE);
+    }
+
+    protected AbstractSpecification2(final Nullability nullability, final TypeChecking typeChecking) {
+        this.expectedType = findExpectedType(getClass());
+        this.nullability = nullability;
+        this.typeChecking = typeChecking;
+    }
+
+    @Override
+    @SuppressWarnings({ "unchecked" })
+    public final String satisfies(final Object obj) {
+        // unused because satisfiesTranslatable will be called instead.
+        return null;
+    }
+
+    /**
+     * Checks not null and is correct type, and delegates to
+     * {@link #satisfiesTranslatableSafely(Object)}.
+     */
+    public TranslatableString satisfiesTranslatable(final Object obj) {
+        if (obj == null) {
+            return nullability == Nullability.IGNORE_IF_NULL
+                    ? null
+                    : TranslatableString.tr("Cannot be null");
+        }
+        if (!expectedType.isInstance(obj)) {
+            return typeChecking == TypeChecking.IGNORE_INCORRECT_TYPE
+                    ? null
+                    : TranslatableString.tr("Incorrect type");
+        }
+        final T objAsT = (T) obj;
+        return satisfiesTranslatableSafely(objAsT);
+
+    }
+
+    public abstract TranslatableString satisfiesTranslatableSafely(T obj);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/applib/src/main/java/org/apache/isis/applib/spec/Specification2.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/spec/Specification2.java b/core/applib/src/main/java/org/apache/isis/applib/spec/Specification2.java
new file mode 100644
index 0000000..c4d8233
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/spec/Specification2.java
@@ -0,0 +1,38 @@
+/*
+ *  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.spec;
+
+import org.apache.isis.applib.services.i18n.TranslatableString;
+
+/**
+ * Optional extension to the base {@link Specification}, to allow for i18n.
+ *
+ * <p>
+ *     If implemented, then the {@link #satisfies(Object)} inherited from {@link Specification} can just return <tt>null</tt>;
+ *     it will never be called by the framework.
+ * </p>
+ */
+public interface Specification2 extends Specification {
+
+    /**
+     * If <tt>null</tt> then satisfied, otherwise is the reason (as a {@link TranslatableString} translatable string) as to why the specification is not satisfied.
+     */
+    public TranslatableString satisfiesTranslatable(Object obj);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecification2Test.java
----------------------------------------------------------------------
diff --git a/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecification2Test.java b/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecification2Test.java
new file mode 100644
index 0000000..e4832fb
--- /dev/null
+++ b/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecification2Test.java
@@ -0,0 +1,92 @@
+/*
+ *  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.spec;
+
+import org.junit.Test;
+
+import org.apache.isis.applib.services.i18n.TranslatableString;
+import org.apache.isis.applib.spec.AbstractSpecification2.Nullability;
+import org.apache.isis.applib.spec.AbstractSpecification2.TypeChecking;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class AbstractSpecification2Test {
+
+    private AbstractSpecification2<String> specAbstractSomeDomainObject;
+
+    @Test
+    public void shouldSatisfyByDefaultForNull() {
+        specAbstractSomeDomainObject = new AbstractSpecification2<String>() {
+            @Override
+            public TranslatableString satisfiesTranslatableSafely(final String obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfiesTranslatable(null), is(nullValue()));
+    }
+
+    @Test
+    public void shouldNotSatisfyForNullIfConfiguredAsSuch() {
+        specAbstractSomeDomainObject = new AbstractSpecification2<String>(Nullability.ENSURE_NOT_NULL, TypeChecking.IGNORE_INCORRECT_TYPE) {
+            @Override
+            public TranslatableString satisfiesTranslatableSafely(final String obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfiesTranslatable(null), is(not(nullValue())));
+    }
+
+    @Test
+    public void shouldSatisfyByDefaultForIncorrectType() {
+        specAbstractSomeDomainObject = new AbstractSpecification2<String>() {
+            @Override
+            public TranslatableString satisfiesTranslatableSafely(final String obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfiesTranslatable(new Integer(1)), is(nullValue()));
+    }
+
+    @Test
+    public void shouldNotSatisfyForIncorrectTypeIfConfiguredAsSuch() {
+        specAbstractSomeDomainObject = new AbstractSpecification2<String>(Nullability.IGNORE_IF_NULL, TypeChecking.ENSURE_CORRECT_TYPE) {
+            @Override
+            public TranslatableString satisfiesTranslatableSafely(final String obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfiesTranslatable(new Integer(1)), is(not(nullValue())));
+    }
+
+    @Test
+    public void shouldSatisfyForNonNullCorrectTypeIfConfiguredAsSuch() {
+        specAbstractSomeDomainObject = new AbstractSpecification2<String>(Nullability.ENSURE_NOT_NULL, TypeChecking.ENSURE_CORRECT_TYPE) {
+            @Override
+            public TranslatableString satisfiesTranslatableSafely(final String obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfiesTranslatable(new String()), is(nullValue()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecificationTest.java
----------------------------------------------------------------------
diff --git a/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecificationTest.java b/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecificationTest.java
new file mode 100644
index 0000000..99e13d0
--- /dev/null
+++ b/core/applib/src/test/java/org/apache/isis/applib/spec/AbstractSpecificationTest.java
@@ -0,0 +1,97 @@
+/*
+ *  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.spec;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+import org.apache.isis.applib.spec.AbstractSpecification.Nullability;
+import org.apache.isis.applib.spec.AbstractSpecification.TypeChecking;
+
+public class AbstractSpecificationTest {
+
+    private static class SomeDomainObject {
+    }
+
+    private static class SomeOtherDomainObject {
+    }
+
+    private AbstractSpecification<SomeDomainObject> specAbstractSomeDomainObject;
+
+    @Test
+    public void shouldSatisfyByDefaultForNull() {
+        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>() {
+            @Override
+            public String satisfiesSafely(final SomeDomainObject obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfies(null), is(nullValue()));
+    }
+
+    @Test
+    public void shouldNotSatisfyForNullIfConfiguredAsSuch() {
+        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>(Nullability.ENSURE_NOT_NULL, TypeChecking.IGNORE_INCORRECT_TYPE) {
+            @Override
+            public String satisfiesSafely(final SomeDomainObject obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfies(null), is(not(nullValue())));
+    }
+
+    @Test
+    public void shouldSatisfyByDefaultForIncorrectType() {
+        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>() {
+            @Override
+            public String satisfiesSafely(final SomeDomainObject obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfies(new SomeOtherDomainObject()), is(nullValue()));
+    }
+
+    @Test
+    public void shouldNotSatisfyForIncorrectTypeIfConfiguredAsSuch() {
+        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>(Nullability.IGNORE_IF_NULL, TypeChecking.ENSURE_CORRECT_TYPE) {
+            @Override
+            public String satisfiesSafely(final SomeDomainObject obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfies(new SomeOtherDomainObject()), is(not(nullValue())));
+    }
+
+    @Test
+    public void shouldSatisfyForNonNullCorrectTypeIfConfiguredAsSuch() {
+        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>(Nullability.ENSURE_NOT_NULL, TypeChecking.ENSURE_CORRECT_TYPE) {
+            @Override
+            public String satisfiesSafely(final SomeDomainObject obj) {
+                return null;
+            }
+        };
+        assertThat(specAbstractSomeDomainObject.satisfies(new SomeDomainObject()), is(nullValue()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/applib/src/test/java/org/apache/isis/applib/spec/SpecificationAbstractTest.java
----------------------------------------------------------------------
diff --git a/core/applib/src/test/java/org/apache/isis/applib/spec/SpecificationAbstractTest.java b/core/applib/src/test/java/org/apache/isis/applib/spec/SpecificationAbstractTest.java
deleted file mode 100644
index 5650c20..0000000
--- a/core/applib/src/test/java/org/apache/isis/applib/spec/SpecificationAbstractTest.java
+++ /dev/null
@@ -1,97 +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.spec;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-
-import org.junit.Test;
-
-import org.apache.isis.applib.spec.AbstractSpecification.Nullability;
-import org.apache.isis.applib.spec.AbstractSpecification.TypeChecking;
-
-public class SpecificationAbstractTest {
-
-    private static class SomeDomainObject {
-    }
-
-    private static class SomeOtherDomainObject {
-    }
-
-    private AbstractSpecification<SomeDomainObject> specAbstractSomeDomainObject;
-
-    @Test
-    public void shouldSatisfyByDefaultForNull() {
-        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>() {
-            @Override
-            public String satisfiesSafely(final SomeDomainObject obj) {
-                return null;
-            }
-        };
-        assertThat(specAbstractSomeDomainObject.satisfies(null), is(nullValue()));
-    }
-
-    @Test
-    public void shouldNotSatisfyForNullIfConfiguredAsSuch() {
-        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>(Nullability.ENSURE_NOT_NULL, TypeChecking.IGNORE_INCORRECT_TYPE) {
-            @Override
-            public String satisfiesSafely(final SomeDomainObject obj) {
-                return null;
-            }
-        };
-        assertThat(specAbstractSomeDomainObject.satisfies(null), is(not(nullValue())));
-    }
-
-    @Test
-    public void shouldSatisfyByDefaultForIncorrectType() {
-        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>() {
-            @Override
-            public String satisfiesSafely(final SomeDomainObject obj) {
-                return null;
-            }
-        };
-        assertThat(specAbstractSomeDomainObject.satisfies(new SomeOtherDomainObject()), is(nullValue()));
-    }
-
-    @Test
-    public void shouldNotSatisfyForIncorrectTypeIfConfiguredAsSuch() {
-        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>(Nullability.IGNORE_IF_NULL, TypeChecking.ENSURE_CORRECT_TYPE) {
-            @Override
-            public String satisfiesSafely(final SomeDomainObject obj) {
-                return null;
-            }
-        };
-        assertThat(specAbstractSomeDomainObject.satisfies(new SomeOtherDomainObject()), is(not(nullValue())));
-    }
-
-    @Test
-    public void shouldSatisfyForNonNullCorrectTypeIfConfiguredAsSuch() {
-        specAbstractSomeDomainObject = new AbstractSpecification<SomeDomainObject>(Nullability.ENSURE_NOT_NULL, TypeChecking.ENSURE_CORRECT_TYPE) {
-            @Override
-            public String satisfiesSafely(final SomeDomainObject obj) {
-                return null;
-            }
-        };
-        assertThat(specAbstractSomeDomainObject.satisfies(new SomeDomainObject()), is(nullValue()));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet.java
index 7efb6f8..f8cc8dc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet.java
@@ -22,15 +22,18 @@ package org.apache.isis.core.metamodel.facets.object.validating.mustsatisfyspec;
 import java.util.List;
 
 import org.apache.isis.applib.events.ValidityEvent;
+import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.spec.Specification;
-import org.apache.isis.applib.util.ReasonBuffer;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
+import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.SpecificationEvaluator;
 import org.apache.isis.core.metamodel.interactions.ProposedHolder;
 import org.apache.isis.core.metamodel.interactions.ValidatingInteractionAdvisor;
 import org.apache.isis.core.metamodel.interactions.ValidityContext;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 
 /**
  * @deprecated
@@ -44,9 +47,20 @@ public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet extend
 
     private final List<Specification> specifications;
 
-    public MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(final List<Specification> specifications, final FacetHolder holder) {
+    private final SpecificationEvaluator specificationEvaluator;
+
+    public MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(
+            final List<Specification> specifications,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         super(type(), holder, Derivation.NOT_DERIVED);
         this.specifications = specifications;
+
+        final TranslationService translationService = servicesInjector.lookupService(TranslationService.class);
+        // sadness: same as in TranslationFactory
+        final String translationContext = ((IdentifiedHolder) holder).getIdentifier().toClassAndNameIdentityString();
+
+        specificationEvaluator = new SpecificationEvaluator(translationService, translationContext);
     }
 
     @Override
@@ -57,11 +71,9 @@ public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet extend
         final ProposedHolder proposedHolder = (ProposedHolder) validityContext;
         final ObjectAdapter targetNO = proposedHolder.getProposed();
         final Object targetObject = targetNO.getObject();
-        final ReasonBuffer buf = new ReasonBuffer();
-        for (final Specification specification : specifications) {
-            buf.append(specification.satisfies(targetObject));
-        }
-        return buf.getReason();
+        return specificationEvaluator.evaluation()
+                .evaluate(specifications, targetObject)
+                .getReason();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory.java
index 9196e11..a495d47 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/validating/mustsatisfyspec/MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory.java
@@ -32,6 +32,8 @@ import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
 import org.apache.isis.core.metamodel.facets.Annotations;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjectorAware;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForDeprecatedAnnotation;
 
@@ -39,7 +41,7 @@ import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorFor
  * @deprecated
  */
 @Deprecated
-public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner, IsisConfigurationAware {
+public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner, IsisConfigurationAware, ServicesInjectorAware {
 
     private final MetaModelValidatorForDeprecatedAnnotation validator = new MetaModelValidatorForDeprecatedAnnotation(MustSatisfy.class);
 
@@ -49,15 +51,18 @@ public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory
 
     @Override
     public void process(final ProcessClassContext processClassContaxt) {
-        final Facet facet = create(processClassContaxt.getCls(), processClassContaxt.getFacetHolder());
+        final Facet facet = create(processClassContaxt.getCls(), processClassContaxt.getFacetHolder(), servicesInjector);
         FacetUtil.addFacet(validator.flagIfPresent(facet));
     }
 
-    private Facet create(final Class<?> clazz, final FacetHolder holder) {
-        return create(Annotations.getAnnotation(clazz, MustSatisfy.class), holder);
+    private Facet create(final Class<?> clazz, final FacetHolder holder, final ServicesInjector servicesInjector) {
+        return create(Annotations.getAnnotation(clazz, MustSatisfy.class), holder, servicesInjector);
     }
 
-    private static Facet create(final MustSatisfy annotation, final FacetHolder holder) {
+    private static Facet create(
+            final MustSatisfy annotation,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         if (annotation == null) {
             return null;
         }
@@ -69,7 +74,7 @@ public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory
                 specifications.add(specification);
             }
         }
-        return specifications.size() > 0 ? new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(specifications, holder) : null;
+        return specifications.size() > 0 ? new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(specifications, holder, servicesInjector) : null;
     }
 
     private static Specification newSpecificationElseNull(final Class<?> value) {
@@ -95,4 +100,11 @@ public class MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacetFactory
         validator.setConfiguration(configuration);
     }
 
+
+    private ServicesInjector servicesInjector;
+
+    @Override
+    public void setServicesInjector(final ServicesInjector servicesInjector) {
+        this.servicesInjector = servicesInjector;
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
index 7e22b51..19345c0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
@@ -21,17 +21,20 @@ package org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec;
 
 import java.util.List;
 import org.apache.isis.applib.events.ValidityEvent;
+import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.spec.Specification;
-import org.apache.isis.applib.util.ReasonBuffer;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
 import org.apache.isis.core.metamodel.interactions.ProposedHolder;
 import org.apache.isis.core.metamodel.interactions.ValidityContext;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 
 public abstract class MustSatisfySpecificationFacetAbstract extends FacetAbstract implements MustSatisfySpecificationFacet {
 
+
     public static Class<? extends Facet> type() {
         return MustSatisfySpecificationFacet.class;
     }
@@ -45,9 +48,20 @@ public abstract class MustSatisfySpecificationFacetAbstract extends FacetAbstrac
         return specifications;
     }
 
-    public MustSatisfySpecificationFacetAbstract(final List<Specification> specifications, final FacetHolder holder) {
+    private final SpecificationEvaluator specificationEvaluator;
+
+    public MustSatisfySpecificationFacetAbstract(
+            final List<Specification> specifications,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         super(type(), holder, Derivation.NOT_DERIVED);
         this.specifications = specifications;
+
+        final TranslationService translationService = servicesInjector.lookupService(TranslationService.class);
+        // sadness: same as in TranslationFactory
+        final String translationContext = ((IdentifiedHolder) holder).getIdentifier().toClassAndNameIdentityString();
+
+        specificationEvaluator = new SpecificationEvaluator(translationService, translationContext);
     }
 
     @Override
@@ -61,11 +75,9 @@ public abstract class MustSatisfySpecificationFacetAbstract extends FacetAbstrac
             return null;
         }
         final Object proposedObject = proposedAdapter.getObject();
-        final ReasonBuffer buf = new ReasonBuffer();
-        for (final Specification specification : specifications) {
-            buf.append(specification.satisfies(proposedObject));
-        }
-        return buf.getReason();
+        return specificationEvaluator.evaluation()
+                .evaluate(specifications, proposedObject)
+                .getReason();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/SpecificationEvaluator.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/SpecificationEvaluator.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/SpecificationEvaluator.java
new file mode 100644
index 0000000..aa2b445
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/SpecificationEvaluator.java
@@ -0,0 +1,80 @@
+/*
+ *  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.core.metamodel.facets.objectvalue.mustsatisfyspec;
+
+import java.util.List;
+
+import org.apache.isis.applib.services.i18n.TranslatableString;
+import org.apache.isis.applib.services.i18n.TranslationService;
+import org.apache.isis.applib.spec.Specification;
+import org.apache.isis.applib.spec.Specification2;
+import org.apache.isis.applib.util.ReasonBuffer;
+
+/**
+ * Encapsulates the algorithm for evaluating {@link Specification}s that might optionally be
+ * translatable (ie extend {@link Specification2}).
+ */
+public class SpecificationEvaluator {
+
+    private final TranslationService translationService;
+    private final String translationContext;
+
+    public SpecificationEvaluator(
+            final TranslationService translationService,
+            final String translationContext) {
+        this.translationService = translationService;
+        this.translationContext = translationContext;
+    }
+
+    public Evaluation evaluation() {
+        return new Evaluation();
+    }
+
+    public class Evaluation {
+
+        final ReasonBuffer reasonBuffer = new ReasonBuffer();
+
+        public Evaluation evaluate(final List<Specification> specifications, final Object proposedObject) {
+            for (final Specification specification : specifications) {
+                evaluate(specification, proposedObject);
+            }
+            return this;
+        }
+
+        public Evaluation evaluate(final Specification specification, final Object proposedObject) {
+            if (specification instanceof Specification2) {
+                final Specification2 specification2 = (Specification2) specification;
+                final TranslatableString translatableReason = specification2.satisfiesTranslatable(proposedObject);
+                if (translatableReason != null) {
+                    final String translatedReason = translatableReason.translate(translationService, translationContext);
+                    reasonBuffer.append(translatedReason);
+                }
+            } else {
+                final String satisfies = specification.satisfies(proposedObject);
+                reasonBuffer.append(satisfies);
+            }
+            return this;
+        }
+
+        public String getReason() {
+            return reasonBuffer.getReason();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
index 8ee1e8b..f416911 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
@@ -45,10 +45,12 @@ import org.apache.isis.core.metamodel.facets.param.parameter.mustsatisfy.MustSat
 import org.apache.isis.core.metamodel.facets.param.parameter.mustsatisfy.MustSatisfySpecificationFacetForParameterAnnotation;
 import org.apache.isis.core.metamodel.facets.param.parameter.regex.RegExFacetForParameterAnnotation;
 import org.apache.isis.core.metamodel.facets.param.parameter.regex.RegExFacetFromRegExAnnotationOnParameter;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjectorAware;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForDeprecatedAnnotation;
 
-public class ParameterAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner, IsisConfigurationAware {
+public class ParameterAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner, IsisConfigurationAware, ServicesInjectorAware {
 
     private final MetaModelValidatorForDeprecatedAnnotation maxLengthValidator = new MetaModelValidatorForDeprecatedAnnotation(MaxLength.class);
     private final MetaModelValidatorForDeprecatedAnnotation mustSatisfyValidator = new MetaModelValidatorForDeprecatedAnnotation(MustSatisfy.class);
@@ -115,7 +117,7 @@ public class ParameterAnnotationFacetFactory extends FacetFactoryAbstract implem
         for (final Annotation parameterAnnotation : parameterAnnotations) {
             if (parameterAnnotation instanceof MustSatisfy) {
                 final MustSatisfy annotation = (MustSatisfy) parameterAnnotation;
-                final Facet facet = MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.create(annotation, processParameterContext.getFacetHolder());
+                final Facet facet = MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.create(annotation, processParameterContext.getFacetHolder(), servicesInjector);
                 FacetUtil.addFacet(mustSatisfyValidator.flagIfPresent(facet, processParameterContext));
                 return;
             }
@@ -125,7 +127,7 @@ public class ParameterAnnotationFacetFactory extends FacetFactoryAbstract implem
             if (parameterAnnotation instanceof Parameter) {
                 final Parameter parameter = (Parameter) parameterAnnotation;
                 FacetUtil.addFacet(
-                        MustSatisfySpecificationFacetForParameterAnnotation.create(parameter, holder));
+                        MustSatisfySpecificationFacetForParameterAnnotation.create(parameter, holder, servicesInjector));
                 return;
             }
         }
@@ -211,4 +213,11 @@ public class ParameterAnnotationFacetFactory extends FacetFactoryAbstract implem
         optionalValidator.setConfiguration(configuration);
     }
 
+
+    private ServicesInjector servicesInjector;
+
+    @Override
+    public void setServicesInjector(final ServicesInjector servicesInjector) {
+        this.servicesInjector = servicesInjector;
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
index 5fb37f9..937d027 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
@@ -26,6 +26,7 @@ import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSatisfySpecificationFacetAbstract;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 
 /**
  * @deprecated
@@ -34,7 +35,10 @@ import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSat
 public class MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter extends MustSatisfySpecificationFacetAbstract {
 
 
-    public static Facet create(final MustSatisfy annotation, final FacetHolder holder) {
+    public static Facet create(
+            final MustSatisfy annotation,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         if (annotation == null) {
             return null;
         }
@@ -46,11 +50,13 @@ public class MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter ex
                 specifications.add(specification);
             }
         }
-        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter(specifications, holder) : null;
+        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter(specifications, holder, servicesInjector) : null;
     }
 
-    private MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter(final List<Specification> specifications, final FacetHolder holder) {
-        super(specifications, holder);
+    private MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter(
+            final List<Specification> specifications,
+            final FacetHolder holder, final ServicesInjector servicesInjector) {
+        super(specifications, holder, servicesInjector);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
index c508ed0..c597fa6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
@@ -26,10 +26,14 @@ import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSatisfySpecificationFacetAbstract;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 
 public class MustSatisfySpecificationFacetForParameterAnnotation extends MustSatisfySpecificationFacetAbstract {
 
-    public static Facet create(final Parameter parameter, final FacetHolder holder) {
+    public static Facet create(
+            final Parameter parameter,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
 
         if (parameter == null) {
             return null;
@@ -43,12 +47,15 @@ public class MustSatisfySpecificationFacetForParameterAnnotation extends MustSat
                 specifications.add(specification);
             }
         }
-        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForParameterAnnotation(specifications, holder) : null;
+        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForParameterAnnotation(specifications, holder, servicesInjector) : null;
     }
 
 
-    private MustSatisfySpecificationFacetForParameterAnnotation(final List<Specification> specifications, final FacetHolder holder) {
-        super(specifications, holder);
+    private MustSatisfySpecificationFacetForParameterAnnotation(
+            final List<Specification> specifications,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        super(specifications, holder, servicesInjector);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
index cc02bfe..6ec1109 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
@@ -290,12 +290,12 @@ public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract impleme
 
         // check for deprecated @MustSatisfy first
         final MustSatisfy annotation = Annotations.getAnnotation(method, MustSatisfy.class);
-        Facet facet = mustSatisfyValidator.flagIfPresent(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.create(annotation, holder), processMethodContext);
+        Facet facet = mustSatisfyValidator.flagIfPresent(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.create(annotation, holder, servicesInjector), processMethodContext);
 
         // else search for @Property(mustSatisfy=...)
         final Property property = Annotations.getAnnotation(method, Property.class);
         if(facet == null) {
-            facet = MustSatisfySpecificationFacetForPropertyAnnotation.create(property, holder);
+            facet = MustSatisfySpecificationFacetForPropertyAnnotation.create(property, holder, servicesInjector);
         }
 
         FacetUtil.addFacet(facet);

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
index 2419a4d..a5d93be 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
@@ -26,6 +26,7 @@ import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSatisfySpecificationFacetAbstract;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 
 /**
  * @deprecated
@@ -33,7 +34,10 @@ import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSat
 @Deprecated
 public class MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty extends MustSatisfySpecificationFacetAbstract {
 
-    public static Facet create(final MustSatisfy annotation, final FacetHolder holder) {
+    public static Facet create(
+            final MustSatisfy annotation,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         if (annotation == null) {
             return null;
         }
@@ -45,12 +49,15 @@ public class MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty ext
                 specifications.add(specification);
             }
         }
-        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty(specifications, holder) : null;
+        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty(specifications, holder, servicesInjector) : null;
     }
 
 
-    private MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty(final List<Specification> specifications, final FacetHolder holder) {
-        super(specifications, holder);
+    private MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty(
+            final List<Specification> specifications,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        super(specifications, holder, servicesInjector);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
index 82bb625..549ecfa 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
@@ -26,12 +26,14 @@ import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSatisfySpecificationFacetAbstract;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 
 public class MustSatisfySpecificationFacetForPropertyAnnotation extends MustSatisfySpecificationFacetAbstract {
 
     public static Facet create(
             final Property property,
-            final FacetHolder holder) {
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
 
         if (property == null) {
             return null;
@@ -45,12 +47,12 @@ public class MustSatisfySpecificationFacetForPropertyAnnotation extends MustSati
                 specifications.add(specification);
             }
         }
-        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForPropertyAnnotation(specifications, holder) : null;
+        return specifications.size() > 0 ? new MustSatisfySpecificationFacetForPropertyAnnotation(specifications, holder, servicesInjector) : null;
     }
 
 
-    private MustSatisfySpecificationFacetForPropertyAnnotation(final List<Specification> specifications, final FacetHolder holder) {
-        super(specifications, holder);
+    private MustSatisfySpecificationFacetForPropertyAnnotation(final List<Specification> specifications, final FacetHolder holder, final ServicesInjector servicesInjector) {
+        super(specifications, holder, servicesInjector);
     }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/parameter/MustSatisfySpecificationFacetFactoryProcessParameterTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/parameter/MustSatisfySpecificationFacetFactoryProcessParameterTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/parameter/MustSatisfySpecificationFacetFactoryProcessParameterTest.java
index 796775d..56f5894 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/parameter/MustSatisfySpecificationFacetFactoryProcessParameterTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/parameter/MustSatisfySpecificationFacetFactoryProcessParameterTest.java
@@ -26,11 +26,13 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessParameterContext;
 import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
 import org.apache.isis.core.metamodel.facets.param.parameter.mustsatisfy.MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter;
 import org.apache.isis.core.metamodel.facets.propparam.specification.DomainObjectWithMustSatisfyAnnotations;
 import org.apache.isis.core.metamodel.facets.propparam.specification.DomainObjectWithoutMustSatisfyAnnotations;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
 
 import static org.apache.isis.core.commons.matchers.IsisMatchers.anInstanceOf;
@@ -43,6 +45,13 @@ public class MustSatisfySpecificationFacetFactoryProcessParameterTest {
     @Mock
     private FacetedMethodParameter mockFacetedMethodParameter;
 
+    @Mock
+    private ServicesInjector mockServicesInjector;
+
+    @Mock
+    private TranslationService mockTranslationService;
+
+
     private Class<DomainObjectWithoutMustSatisfyAnnotations> domainObjectClassWithoutAnnotation;
     private Class<DomainObjectWithMustSatisfyAnnotations> domainObjectClassWithAnnotation;
     private Method changeLastNameMethodWithout;
@@ -54,6 +63,13 @@ public class MustSatisfySpecificationFacetFactoryProcessParameterTest {
 
     @Before
     public void setUp() throws Exception {
+
+        context.checking(new Expectations() {{
+            allowing(mockServicesInjector).lookupService(TranslationService.class);
+            will(returnValue(mockTranslationService));
+        }});
+
+
         domainObjectClassWithoutAnnotation = DomainObjectWithoutMustSatisfyAnnotations.class;
         domainObjectClassWithAnnotation = DomainObjectWithMustSatisfyAnnotations.class;
         changeLastNameMethodWithout = domainObjectClassWithoutAnnotation.getMethod("changeLastName", String.class);
@@ -67,6 +83,7 @@ public class MustSatisfySpecificationFacetFactoryProcessParameterTest {
         });
 
         facetFactory = new ParameterAnnotationFacetFactory();
+        facetFactory.setServicesInjector(mockServicesInjector);
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/property/MustSatisfySpecificationFacetFactoryProcessPropertyTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/property/MustSatisfySpecificationFacetFactoryProcessPropertyTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/property/MustSatisfySpecificationFacetFactoryProcessPropertyTest.java
index 1d99589..86fd746 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/property/MustSatisfySpecificationFacetFactoryProcessPropertyTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/property/MustSatisfySpecificationFacetFactoryProcessPropertyTest.java
@@ -20,18 +20,22 @@
 package org.apache.isis.core.metamodel.facets.properties.property;
 
 import java.lang.reflect.Method;
+
 import org.jmock.Expectations;
 import org.jmock.auto.Mock;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.core.metamodel.facetapi.MethodRemover;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext;
 import org.apache.isis.core.metamodel.facets.FacetedMethod;
 import org.apache.isis.core.metamodel.facets.properties.property.mustsatisfy.MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty;
 import org.apache.isis.core.metamodel.facets.propparam.specification.DomainObjectWithMustSatisfyAnnotations;
 import org.apache.isis.core.metamodel.facets.propparam.specification.DomainObjectWithoutMustSatisfyAnnotations;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
 
 import static org.apache.isis.core.commons.matchers.IsisMatchers.anInstanceOf;
@@ -44,7 +48,13 @@ public class MustSatisfySpecificationFacetFactoryProcessPropertyTest {
     @Mock
     private MethodRemover mockMethodRemover;
     @Mock
-    private FacetedMethod mockFacetHolder;
+    private FacetedMethod mockFacetedMethod;
+
+    @Mock
+    private ServicesInjector mockServicesInjector;
+
+    @Mock
+    private TranslationService mockTranslationService;
 
     private Class<DomainObjectWithoutMustSatisfyAnnotations> domainObjectClassWithoutAnnotation;
     private Class<DomainObjectWithMustSatisfyAnnotations> domainObjectClassWithAnnotation;
@@ -56,6 +66,12 @@ public class MustSatisfySpecificationFacetFactoryProcessPropertyTest {
 
     @Before
     public void setUp() throws Exception {
+
+        context.checking(new Expectations() {{
+            allowing(mockServicesInjector).lookupService(TranslationService.class);
+            will(returnValue(mockTranslationService));
+        }});
+
         domainObjectClassWithoutAnnotation = DomainObjectWithoutMustSatisfyAnnotations.class;
         domainObjectClassWithAnnotation = DomainObjectWithMustSatisfyAnnotations.class;
         firstNameMethodWithout = domainObjectClassWithoutAnnotation.getMethod("getFirstName");
@@ -63,12 +79,13 @@ public class MustSatisfySpecificationFacetFactoryProcessPropertyTest {
 
         context.checking(new Expectations() {
             {
-                allowing(mockFacetHolder).getIdentifier();
+                allowing(mockFacetedMethod).getIdentifier();
                 will(returnValue(Identifier.actionIdentifier(Customer.class, "foo")));
             }
         });
 
         facetFactory = new PropertyAnnotationFacetFactory();
+        facetFactory.setServicesInjector(mockServicesInjector);
     }
 
     @Test
@@ -76,10 +93,10 @@ public class MustSatisfySpecificationFacetFactoryProcessPropertyTest {
 
         context.checking(new Expectations() {
             {
-                oneOf(mockFacetHolder).addFacet(with(anInstanceOf(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.class)));
+                oneOf(mockFacetedMethod).addFacet(with(anInstanceOf(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.class)));
             }
         });
-        facetFactory.processMustSatisfy(new ProcessMethodContext(domainObjectClassWithAnnotation.getClass(), null, null, firstNameMethodWith, mockMethodRemover, mockFacetHolder));
+        facetFactory.processMustSatisfy(new ProcessMethodContext(domainObjectClassWithAnnotation.getClass(), null, null, firstNameMethodWith, mockMethodRemover, mockFacetedMethod));
     }
 
     @Test
@@ -87,10 +104,10 @@ public class MustSatisfySpecificationFacetFactoryProcessPropertyTest {
 
         context.checking(new Expectations() {
             {
-                never(mockFacetHolder).addFacet(with(anInstanceOf(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.class)));
+                never(mockFacetedMethod).addFacet(with(anInstanceOf(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.class)));
             }
         });
-        facetFactory.processMustSatisfy(new ProcessMethodContext(domainObjectClassWithAnnotation.getClass(), null, null, firstNameMethodWithout, mockMethodRemover, mockFacetHolder));
+        facetFactory.processMustSatisfy(new ProcessMethodContext(domainObjectClassWithAnnotation.getClass(), null, null, firstNameMethodWithout, mockMethodRemover, mockFacetedMethod));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionMoreTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionMoreTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionMoreTest.java
index c9ee637..c4ea7f5 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionMoreTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionMoreTest.java
@@ -20,48 +20,71 @@
 package org.apache.isis.core.metamodel.facets.propparam.specification;
 
 import org.jmock.Expectations;
+import org.jmock.auto.Mock;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
-import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
 import org.apache.isis.core.metamodel.facets.object.validating.mustsatisfyspec.MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet;
+import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
 
-import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
 
 public class MustSatisfySpecificationValidatingInteractionMoreTest {
 
     @Rule
-    public JUnitRuleMockery2 mockery = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
+    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
 
     private MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet facetForSpecificationFirstLetterUpperCase;
-    private FacetHolder mockHolder;
 
+    @Mock
+    private IdentifiedHolder identifiedHolder;
+
+    @Mock
+    private ServicesInjector mockServicesInjector;
+
+    @Mock
+    private TranslationService mockTranslationService;
+
+    @Mock
     private PropertyModifyContext mockContext;
 
     private ObjectAdapter mockProposedObjectAdapter;
 
     private SpecificationRequiresFirstLetterToBeUpperCase requiresFirstLetterToBeUpperCase;
 
+    public static class Customer {}
     @Before
     public void setUp() throws Exception {
-        mockHolder = mockery.mock(IdentifiedHolder.class);
+
+        identifiedHolder = new AbstractFacetFactoryTest.IdentifiedHolderImpl(Identifier.propertyOrCollectionIdentifier(Customer.class, "lastName"));
+
+        context.checking(new Expectations() {{
+            allowing(mockServicesInjector).lookupService(TranslationService.class);
+            will(returnValue(mockTranslationService));
+        }});
+
         requiresFirstLetterToBeUpperCase = new SpecificationRequiresFirstLetterToBeUpperCase();
 
-        facetForSpecificationFirstLetterUpperCase = new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(Utils.listOf(requiresFirstLetterToBeUpperCase), mockHolder);
+        facetForSpecificationFirstLetterUpperCase = new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(Utils.listOf(requiresFirstLetterToBeUpperCase), identifiedHolder, mockServicesInjector);
 
-        mockContext = mockery.mock(PropertyModifyContext.class);
-        mockProposedObjectAdapter = mockery.mock(ObjectAdapter.class, "proposed");
+        mockProposedObjectAdapter = context.mock(ObjectAdapter.class, "proposed");
     }
 
     @After
     public void tearDown() throws Exception {
-        mockHolder = null;
+        identifiedHolder = null;
         requiresFirstLetterToBeUpperCase = null;
         mockContext = null;
     }
@@ -78,7 +101,7 @@ public class MustSatisfySpecificationValidatingInteractionMoreTest {
      */
     @Test
     public void validatesUsingSpecificationIfProposedOkay() {
-        mockery.checking(new Expectations() {
+        context.checking(new Expectations() {
             {
                 one(mockContext).getProposed();
                 will(returnValue(mockProposedObjectAdapter));
@@ -94,7 +117,7 @@ public class MustSatisfySpecificationValidatingInteractionMoreTest {
 
     @Test
     public void invalidatesUsingSpecificationIfProposedNotOkay() {
-        mockery.checking(new Expectations() {
+        context.checking(new Expectations() {
             {
                 one(mockContext).getProposed();
                 will(returnValue(mockProposedObjectAdapter));

http://git-wip-us.apache.org/repos/asf/isis/blob/6dd04ac1/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionTest.java
index 07dc2d2..d187435 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/propparam/specification/MustSatisfySpecificationValidatingInteractionTest.java
@@ -20,29 +20,44 @@
 package org.apache.isis.core.metamodel.facets.propparam.specification;
 
 import org.jmock.Expectations;
+import org.jmock.auto.Mock;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
-import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
 import org.apache.isis.core.metamodel.facets.object.validating.mustsatisfyspec.MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet;
+import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
 
-import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
 
 public class MustSatisfySpecificationValidatingInteractionTest {
 
     @Rule
-    public JUnitRuleMockery2 mockery = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
+    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
 
     private MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet facetForSpecificationAlwaysSatisfied;
     private MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet facetForSpecificationNeverSatisfied;
-    private FacetHolder mockHolder;
 
+    private IdentifiedHolder identifiedHolder;
+
+    @Mock
+    private ServicesInjector mockServicesInjector;
+
+    @Mock
+    private TranslationService mockTranslationService;
+
+    @Mock
     private PropertyModifyContext mockContext;
 
     private ObjectAdapter mockProposedObjectAdapter;
@@ -51,20 +66,26 @@ public class MustSatisfySpecificationValidatingInteractionTest {
     private SpecificationAlwaysSatisfied specificationAlwaysSatisfied;
     private SpecificationNeverSatisfied specificationNeverSatisfied;
 
+    public static class Customer {}
+
     @Before
     public void setUp() throws Exception {
-        mockHolder = mockery.mock(IdentifiedHolder.class);
+        identifiedHolder = new AbstractFacetFactoryTest.IdentifiedHolderImpl(Identifier.propertyOrCollectionIdentifier(Customer.class, "lastName"));
+        context.checking(new Expectations() {{
+            allowing(mockServicesInjector).lookupService(TranslationService.class);
+            will(returnValue(mockTranslationService));
+        }});
+
         specificationAlwaysSatisfied = new SpecificationAlwaysSatisfied();
         specificationNeverSatisfied = new SpecificationNeverSatisfied();
 
-        facetForSpecificationAlwaysSatisfied = new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(Utils.listOf(specificationAlwaysSatisfied), mockHolder);
-        facetForSpecificationNeverSatisfied = new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(Utils.listOf(specificationNeverSatisfied), mockHolder);
+        facetForSpecificationAlwaysSatisfied = new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(Utils.listOf(specificationAlwaysSatisfied), identifiedHolder, mockServicesInjector);
+        facetForSpecificationNeverSatisfied = new MustSatisfySpecificationFromMustSatisfyAnnotationOnTypeFacet(Utils.listOf(specificationNeverSatisfied), identifiedHolder, mockServicesInjector);
 
-        mockContext = mockery.mock(PropertyModifyContext.class);
-        mockProposedObjectAdapter = mockery.mock(ObjectAdapter.class, "proposed");
-        mockProposedObject = mockery.mock(Object.class, "proposedObject");
+        mockProposedObjectAdapter = context.mock(ObjectAdapter.class, "proposed");
+        mockProposedObject = context.mock(Object.class, "proposedObject");
 
-        mockery.checking(new Expectations() {
+        context.checking(new Expectations() {
             {
                 one(mockContext).getProposed();
                 will(returnValue(mockProposedObjectAdapter));
@@ -77,7 +98,7 @@ public class MustSatisfySpecificationValidatingInteractionTest {
 
     @After
     public void tearDown() throws Exception {
-        mockHolder = null;
+        identifiedHolder = null;
         facetForSpecificationAlwaysSatisfied = null;
         facetForSpecificationNeverSatisfied = null;
         mockContext = null;