/*
* 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 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 static final String ISIS_REFLECTOR_VALIDATOR_JDOQL_VARIABLES_CLAUSE_KEY = "isis.reflector.validator.jdoqlVariablesClause";
public static final boolean ISIS_REFLECTOR_VALIDATOR_JDOQL_VARIABLES_CLAUSE_DEFAULT = true;
public JdoQueryAnnotationFacetFactory() {
super(FeatureType.OBJECTS_ONLY);
}
@Override
public void process(ProcessClassContext processClassContext) {
final Class<?> cls = processClassContext.getCls();
final Queries namedQueriesAnnotation = Annotations.getAnnotation(cls, Queries.class);
final FacetHolder facetHolder = processClassContext.getFacetHolder();
if (namedQueriesAnnotation != null) {
FacetUtil.addFacet(new JdoQueriesFacetAnnotation(
namedQueriesAnnotation.value(), facetHolder));
return;
}
final Query namedQueryAnnotation = Annotations.getAnnotation(cls, Query.class);
if (namedQueryAnnotation != null) {
FacetUtil.addFacet(new JdoQueryFacetAnnotation(
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(
"%s: error in JDOQL query, 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(
"%s: error in JDOQL query, class name after FROM clause should be same as class name on which annotated, or one of its supertypes (JDOQL : %s)",
className, query);
}
});
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(
"%s: error in JDOQL query, 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(
"%s: error in JDOQL query, 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;
}
}