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/13 09:23:32 UTC

isis git commit: ISIS-1580: adds validators for JdoQueryAnnotation to check the FROM and VARIABLES clauses

Repository: isis
Updated Branches:
  refs/heads/maint-1.14.0 5e247641e -> e97baf565


ISIS-1580: adds validators for JdoQueryAnnotation to check the FROM and VARIABLES clauses


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

Branch: refs/heads/maint-1.14.0
Commit: e97baf565cc34ca013789857d09f2d6d5fbad720
Parents: 5e24764
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Mon Feb 13 07:35:54 2017 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Mon Feb 13 09:23:23 2017 +0000

----------------------------------------------------------------------
 .../guides/_rgcfg_configuring-core.adoc         |  16 ++
 .../validator/MetaModelValidatorVisiting.java   |   2 +-
 .../query/JdoQueryAnnotationFacetFactory.java   | 168 ++++++++++++++++++-
 .../JdoQueryAnnotationFacetFactoryTest.java     | 101 +++++++++++
 .../application/manifest/isis.properties        |   6 +-
 5 files changed, 290 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/e97baf56/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 3a6e1c2..4386605 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgcfg_configuring-core.adoc
@@ -577,6 +577,22 @@ If not, then a metamodel validation error will be triggered, meaning the app won
 +
 See also `isis.reflector.facets.ignoreDeprecated`.
 
+|`isis.reflector.validator.` +
+`jdoqlFromClause`
+|`true`,`false` +
+(`true`)
+| (`1.14.0-SNAPSHOT`) Whether to check that the class name in JDOQL `FROM` clause matches or is a supertype of the class on which it is annotated. +
++
+Only "SELECT" queries are validated; "UPDATE" queries etc are simply ignored.
+
+|`isis.reflector.validator.` +
+`jdoqlVariablesClause`
+|`true`,`false` +
+(`true`)
+| (`1.14.0-SNAPSHOT`) Whether to check that the class name in JDOQL `VARIABLES` clause is a recognized class. +
++
+Note that although JDOQL syntax supports multiple `VARIABLES` classes, currently the validator only checks the first class name found.
+
 
 
 |`isis.viewers.` +

http://git-wip-us.apache.org/repos/asf/isis/blob/e97baf56/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorVisiting.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorVisiting.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorVisiting.java
index edbce71..e7d212a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorVisiting.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorVisiting.java
@@ -23,7 +23,7 @@ import java.util.Collection;
 
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
-public final class MetaModelValidatorVisiting extends MetaModelValidatorAbstract {
+public class MetaModelValidatorVisiting extends MetaModelValidatorAbstract {
 
     public interface Visitor {
         /**

http://git-wip-us.apache.org/repos/asf/isis/blob/e97baf56/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 d58a1f0..74ebc45 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
@@ -18,17 +18,35 @@
  */
 package org.apache.isis.objectstore.jdo.metamodel.facets.object.query;
 
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import javax.jdo.annotations.Queries;
 import javax.jdo.annotations.Query;
 
+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;
 import org.apache.isis.core.metamodel.facets.Annotations;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidator;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting;
+import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
+import org.apache.isis.objectstore.jdo.metamodel.facets.object.persistencecapable.JdoPersistenceCapableFacet;
+
+public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
 
+    public static final String ISIS_REFLECTOR_VALIDATOR_JDOQL_FROM_CLAUSE_KEY = "isis.reflector.validator.jdoqlFromClause";
+    public static final boolean ISIS_REFLECTOR_VALIDATOR_JDOQL_FROM_CLAUSE_DEFAULT = true;
 
-public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract {
+    public static final String ISIS_REFLECTOR_VALIDATOR_JDOQL_VARIABLES_CLAUSE_KEY = "isis.reflector.validator.variablesFromClause";
+    public static final boolean ISIS_REFLECTOR_VALIDATOR_JDOQL_VARIABLES_CLAUSE_DEFAULT = true;
 
     public JdoQueryAnnotationFacetFactory() {
         super(FeatureType.OBJECTS_ONLY);
@@ -52,4 +70,152 @@ public class JdoQueryAnnotationFacetFactory extends FacetFactoryAbstract {
                     namedQueryAnnotation, facetHolder));
         }
     }
+
+    @Override
+    public void refineMetaModelValidator(
+            final MetaModelValidatorComposite metaModelValidator,
+            final IsisConfiguration configuration) {
+
+        final boolean validateFromClause = configuration.getBoolean(
+                                ISIS_REFLECTOR_VALIDATOR_JDOQL_FROM_CLAUSE_KEY,
+                                ISIS_REFLECTOR_VALIDATOR_JDOQL_FROM_CLAUSE_DEFAULT);
+        if (validateFromClause) {
+            final MetaModelValidator queryFromValidator = new MetaModelValidatorVisiting(
+                    new MetaModelValidatorVisiting.Visitor() {
+                        @Override
+                        public boolean visit(
+                                final ObjectSpecification objectSpec,
+                                final ValidationFailures validationFailures) {
+                            validate(objectSpec, validationFailures);
+                            return true;
+                        }
+
+                        private void validate(
+                                final ObjectSpecification objectSpec,
+                                final ValidationFailures validationFailures) {
+                            final JdoQueryFacet facet = objectSpec.getFacet(JdoQueryFacet.class);
+                            if(facet == null) {
+                                return;
+                            }
+                            final List<JdoNamedQuery> namedQueries = facet.getNamedQueries();
+                            for (final JdoNamedQuery namedQuery : namedQueries) {
+                                final String query = namedQuery.getQuery();
+                                final String fromClassName = from(query);
+                                interpret(fromClassName, objectSpec, query, validationFailures);
+                            }
+                        }
+
+                        void interpret(
+                                final String fromClassName, final ObjectSpecification objectSpec,
+                                final String query,
+                                final ValidationFailures validationFailures) {
+
+                            if(fromClassName == null) {
+                                // may be an update statement, for example
+                                return;
+                            }
+
+                            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)",
+                                        className, query);
+                                return;
+
+                            }
+
+                            if (Objects.equals(fromClassName, className)) {
+                                return;
+                            }
+                            final ObjectSpecification fromSpec = getSpecificationLoader().loadSpecification(fromClassName);
+                            List<ObjectSpecification> subclasses = fromSpec.subclasses();
+                            if(subclasses.contains(objectSpec)) {
+                                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)",
+                                    className, query);
+                        }
+
+                    });
+            metaModelValidator.add(queryFromValidator);
+        }
+
+        final boolean validateVariablesClause = configuration.getBoolean(
+                                ISIS_REFLECTOR_VALIDATOR_JDOQL_VARIABLES_CLAUSE_KEY,
+                                ISIS_REFLECTOR_VALIDATOR_JDOQL_VARIABLES_CLAUSE_DEFAULT);
+        if (validateVariablesClause) {
+            final MetaModelValidator queryFromValidator = new MetaModelValidatorVisiting(
+                    new MetaModelValidatorVisiting.Visitor() {
+                        @Override
+                        public boolean visit(
+                                final ObjectSpecification objectSpec,
+                                final ValidationFailures validationFailures) {
+                            validate(objectSpec, validationFailures);
+                            return true;
+                        }
+
+                        private void validate(
+                                final ObjectSpecification objectSpec,
+                                final ValidationFailures validationFailures) {
+                            final String className = objectSpec.getCorrespondingClass().getName();
+                            final JdoQueryFacet facet = objectSpec.getFacet(JdoQueryFacet.class);
+                            if(facet == null) {
+                                return;
+                            }
+                            final List<JdoNamedQuery> namedQueries = facet.getNamedQueries();
+                            for (final JdoNamedQuery namedQuery : namedQueries) {
+                                final String query = namedQuery.getQuery();
+                                final String variablesClassName = variables(query);
+                                interpret(variablesClassName, className, query, validationFailures);
+                            }
+                        }
+
+                        void interpret(
+                                final String variablesClassName, final String className,
+                                final String query,
+                                final ValidationFailures validationFailures) {
+
+                            if(variablesClassName == null) {
+                                return;
+                            }
+
+                            if(!getSpecificationLoader().loaded(variablesClassName)) {
+                                validationFailures.add(
+                                        "Error in JDOQL query on %s, class name for VARIABLES clause not recognized (JDOQL : %s)",
+                                        className, query);
+                                return;
+                            }
+                            ObjectSpecification objectSpecification = getSpecificationLoader()
+                                    .loadSpecification(variablesClassName);
+                            JdoPersistenceCapableFacet persistenceCapableFacet = objectSpecification
+                                    .getFacet(JdoPersistenceCapableFacet.class);
+
+                            if(persistenceCapableFacet == null) {
+                                validationFailures.add(
+                                        "Error in JDOQL query on %s, class name for VARIABLES clause is not annotated as @PersistenceCapable (JDOQL : %s)",
+                                        className, query);
+                                return;
+                            }
+                        }
+                    });
+            metaModelValidator.add(queryFromValidator);
+        }
+    }
+
+    private final static Pattern fromPattern = Pattern.compile("SELECT.*?FROM[\\s]+([^\\s]+).*", Pattern.CASE_INSENSITIVE);
+
+    static String from(final String query) {
+        final Matcher matcher = fromPattern.matcher(query);
+        return matcher.matches() ? matcher.group(1) :  null;
+    }
+
+    private final static Pattern variablesPattern = Pattern.compile(".*?VARIABLES[\\s]+([^\\s]+).*", Pattern.CASE_INSENSITIVE);
+    static String variables(final String query) {
+        final Matcher matcher = variablesPattern.matcher(query);
+        return matcher.matches() ? matcher.group(1) :  null;
+    }
+
+
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/e97baf56/core/metamodel/src/test/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactoryTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactoryTest.java
new file mode 100644
index 0000000..01fca3e
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/objectstore/jdo/metamodel/facets/object/query/JdoQueryAnnotationFacetFactoryTest.java
@@ -0,0 +1,101 @@
+/*
+ *  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.objectstore.jdo.metamodel.facets.object.query;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class JdoQueryAnnotationFacetFactoryTest {
+
+    public static class From_Test extends JdoQueryAnnotationFacetFactoryTest {
+
+        @Test
+        public void lower_case() throws Exception {
+
+            String query = "SELECT from domainapp.modules.simple.dom.impl.SimpleObject WHERE name.indexOf(:name) >= 0 ";
+
+            String name = JdoQueryAnnotationFacetFactory.from(query);
+
+            assertThat(name, is(equalTo("domainapp.modules.simple.dom.impl.SimpleObject")));
+        }
+
+        @Test
+        public void upper_case() throws Exception {
+
+            String query = "SELECT FROM domainapp.modules.simple.dom.impl.SimpleObject WHERE name.indexOf(:name) >= 0 ";
+
+            String name = JdoQueryAnnotationFacetFactory.from(query);
+
+            assertThat(name, is(equalTo("domainapp.modules.simple.dom.impl.SimpleObject")));
+        }
+
+        @Test
+        public void no_match() throws Exception {
+
+            String query = "UPDATE org.isisaddons.module.sessionlogger.dom.SessionLogEntry "
+                    + "   SET logoutTimestamp == :logoutTimestamp "
+                    + "      ,causedBy2 == :causedBy2 "
+                    + " WHERE causedBy2 == null";
+
+            String name = JdoQueryAnnotationFacetFactory.from(query);
+
+            assertThat(name, is(nullValue()));
+        }
+
+    }
+
+    public static class Variables_Test extends JdoQueryAnnotationFacetFactoryTest {
+
+        @Test
+        public void lower_case() throws Exception {
+
+            String query = "SELECT FROM mydomain.Supplier WHERE this.products.contains(prod) && prod.name == 'Beans' variables mydomain.Product prod ";
+
+            String variables = JdoQueryAnnotationFacetFactory.variables(query);
+
+            assertThat(variables, is(equalTo("mydomain.Product")));
+        }
+
+        @Test
+        public void upper_case() throws Exception {
+
+            String query = "SELECT FROM mydomain.Supplier WHERE this.products.contains(prod) && prod.name == 'Beans' VARIABLES mydomain.Product prod ";
+
+            String variables = JdoQueryAnnotationFacetFactory.variables(query);
+
+            assertThat(variables, is(equalTo("mydomain.Product")));
+        }
+
+        @Test
+        public void no_match() throws Exception {
+
+            String query = "SELECT FROM domainapp.modules.simple.dom.impl.SimpleObject WHERE name.indexOf(:name) >= 0 ";
+
+            String variables = JdoQueryAnnotationFacetFactory.variables(query);
+
+            assertThat(variables, is(nullValue()));
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/e97baf56/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 9546cdb..d828d46 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
@@ -17,7 +17,6 @@
 
 
 
-
 #################################################################################
 #
 # MetaModel
@@ -54,6 +53,11 @@ isis.reflector.validator.allowDeprecated=false
 
 
 #
+# Whether to validate JDOQL clauses.  If not specified, default is to validate.
+#
+#isis.reflector.validator.jdoqlFromClause=true
+
+#
 # Whether to ignore or honour (at least some of the) deprecated annotations/method prefixes.
 # If not specified, default is to honour
 #