ClosedConstraintComponent.java
/*******************************************************************************
* Copyright (c) 2020 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents;
import static org.eclipse.rdf4j.model.util.Values.literal;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.ValidationSettings;
import org.eclipse.rdf4j.sail.shacl.ast.CanProduceValidationReport;
import org.eclipse.rdf4j.sail.shacl.ast.ShaclAstLists;
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment;
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher;
import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach;
import org.eclipse.rdf4j.sail.shacl.ast.ValidationQuery;
import org.eclipse.rdf4j.sail.shacl.ast.paths.Path;
import org.eclipse.rdf4j.sail.shacl.ast.paths.SimplePath;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.AbstractBulkJoinPlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BufferedSplitter;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BulkedExternalInnerJoin;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ExternalFilterByQuery;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNodeProvider;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ReduceTargets;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ShiftToPropertyShape;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.TrimToTarget;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnBufferedPlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnionNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Unique;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnorderedSelect;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple;
import org.eclipse.rdf4j.sail.shacl.ast.targets.EffectiveTarget;
import org.eclipse.rdf4j.sail.shacl.results.ValidationResult;
import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;
public class ClosedConstraintComponent extends AbstractConstraintComponent implements CanProduceValidationReport {
private final List<IRI> paths;
private final List<IRI> ignoredProperties;
private final Resource ignoredPropertiesHead;
private final HashSet<IRI> allAllowedPredicates;
private final Shape shape;
public boolean produceValidationReports;
public ClosedConstraintComponent(ShapeSource shapeSource, List<Resource> property, Resource ignoredPropertiesHead,
Shape shape) {
paths = property.stream().flatMap(r -> {
return shapeSource.getObjects(r, ShapeSource.Predicates.PATH)
.map(o -> ((Resource) o))
.map(path -> Path.buildPath(shapeSource, path))
.filter(p -> p instanceof SimplePath)
.map(p -> ((IRI) p.getId()));
}).collect(Collectors.toList());
if (ignoredPropertiesHead != null) {
this.ignoredPropertiesHead = ignoredPropertiesHead;
this.ignoredProperties = ShaclAstLists.toList(shapeSource, ignoredPropertiesHead, IRI.class);
} else {
this.ignoredProperties = Collections.emptyList();
this.ignoredPropertiesHead = null;
}
HashSet<IRI> allAllowedPredicates = new HashSet<>(paths);
allAllowedPredicates.addAll(ignoredProperties);
this.allAllowedPredicates = allAllowedPredicates;
this.shape = shape;
}
public ClosedConstraintComponent(ClosedConstraintComponent closedConstraintComponent) {
paths = closedConstraintComponent.paths;
ignoredProperties = closedConstraintComponent.ignoredProperties;
ignoredPropertiesHead = closedConstraintComponent.ignoredPropertiesHead;
allAllowedPredicates = closedConstraintComponent.allAllowedPredicates;
shape = closedConstraintComponent.shape;
produceValidationReports = closedConstraintComponent.produceValidationReports;
}
@Override
public void toModel(Resource subject, IRI predicate, Model model, Set<Resource> cycleDetection) {
if (!ignoredProperties.isEmpty()) {
model.add(subject, SHACL.IGNORED_PROPERTIES, ignoredPropertiesHead);
if (!model.contains(ignoredPropertiesHead, null, null)) {
ShaclAstLists.listToRdf(ignoredProperties, ignoredPropertiesHead, model);
}
}
model.add(subject, SHACL.CLOSED, literal(true));
}
@Override
public SourceConstraintComponent getConstraintComponent() {
return SourceConstraintComponent.ClosedConstraintComponent;
}
@Override
public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connectionsGroup,
ValidationSettings validationSettings, PlanNodeProvider overrideTargetNode, Scope scope) {
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider = new StatementMatcher.StableRandomVariableProvider();
EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider);
if (scope == Scope.propertyShape) {
Path path = getTargetChain().getPath().get();
PlanNode addedTargets;
if (overrideTargetNode != null) {
addedTargets = effectiveTarget.extend(overrideTargetNode.getPlanNode(), connectionsGroup,
validationSettings.getDataGraph(), scope,
EffectiveTarget.Extend.right,
false, null);
} else {
BufferedSplitter addedTargetsBufferedSplitter = BufferedSplitter.getInstance(
effectiveTarget.getPlanNode(connectionsGroup, validationSettings.getDataGraph(), scope, false,
null));
addedTargets = addedTargetsBufferedSplitter.getPlanNode();
PlanNode addedByPath = path.getAllAdded(connectionsGroup, validationSettings.getDataGraph(), null);
addedByPath = effectiveTarget.getTargetFilter(connectionsGroup,
validationSettings.getDataGraph(),
Unique.getInstance(new TrimToTarget(addedByPath, connectionsGroup), false, connectionsGroup));
addedByPath = new ReduceTargets(addedByPath, addedTargetsBufferedSplitter.getPlanNode(),
connectionsGroup);
addedByPath = effectiveTarget.extend(addedByPath, connectionsGroup, validationSettings.getDataGraph(),
scope,
EffectiveTarget.Extend.left, false,
null);
PlanNode addedByValue = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null,
null, validationSettings.getDataGraph(),
UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> {
return !allAllowedPredicates.contains(statement.getPredicate());
}));
addedByValue = getTargetChain()
.getEffectiveTarget(Scope.nodeShape,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider)
.extend(addedByValue, connectionsGroup, validationSettings.getDataGraph(), Scope.nodeShape,
EffectiveTarget.Extend.left,
false, null);
addedByValue = getTargetChain()
.getEffectiveTarget(Scope.nodeShape,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider)
.getTargetFilter(connectionsGroup, validationSettings.getDataGraph(), addedByValue);
addedTargets = UnionNode.getInstance(connectionsGroup, addedTargets,
new TrimToTarget(new ShiftToPropertyShape(addedByValue, connectionsGroup), connectionsGroup));
addedTargets = UnionNode.getInstance(connectionsGroup, addedByPath, addedTargets);
addedTargets = Unique.getInstance(addedTargets, false, connectionsGroup);
}
PlanNode falseNode = new BulkedExternalInnerJoin(
addedTargets,
connectionsGroup.getBaseConnection(),
validationSettings.getDataGraph(),
path.getTargetQueryFragment(new StatementMatcher.Variable("a"), new StatementMatcher.Variable("c"),
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider, Set.of()),
false,
null,
BulkedExternalInnerJoin.getMapper("a", "c", scope, validationSettings.getDataGraph()),
connectionsGroup, AbstractBulkJoinPlanNode.DEFAULT_VARS);
StatementMatcher.Variable<Value> subjectVariable = stableRandomVariableProvider.next();
StatementMatcher.Variable<Value> predicateVariable = stableRandomVariableProvider.next();
StatementMatcher.Variable<Value> objectVariable = stableRandomVariableProvider.next();
SparqlFragment bgp = SparqlFragment.bgp(List.of(),
subjectVariable.asSparqlVariable() + " " + predicateVariable.asSparqlVariable() + " "
+ objectVariable.asSparqlVariable() + ".",
List.of());
String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( "
+ allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", "))
+ " ) )";
SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of());
SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter));
PlanNode falseNode1 = new ExternalFilterByQuery(connectionsGroup.getBaseConnection(),
validationSettings.getDataGraph(),
falseNode,
sparqlFragment,
subjectVariable,
ValidationTuple::getValue,
(ValidationTuple validationTuple, BindingSet b) -> {
if (produceValidationReports) {
return validationTuple.addValidationResult(t -> {
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(),
b.getValue(objectVariable.getName()),
shape,
this, shape.getSeverity(),
ConstraintComponent.Scope.nodeShape, t.getContexts(),
shape.getContexts());
validationResult.setPathIri(b.getValue(predicateVariable.getName()));
return validationResult;
});
}
return validationTuple;
}, connectionsGroup)
.getTrueNode(UnBufferedPlanNode.class);
return falseNode1;
} else {
assert scope == Scope.nodeShape;
PlanNode targetNodePlanNode;
if (overrideTargetNode != null) {
targetNodePlanNode = getTargetChain()
.getEffectiveTarget(scope, connectionsGroup.getRdfsSubClassOfReasoner(),
stableRandomVariableProvider)
.extend(overrideTargetNode.getPlanNode(), connectionsGroup, validationSettings.getDataGraph(),
scope, EffectiveTarget.Extend.right,
false, null);
} else {
PlanNode addedTargets = effectiveTarget.getPlanNode(connectionsGroup, validationSettings.getDataGraph(),
scope, false, null);
// get all subjects of all triples where the predicate is not in the allAllowedPredicates set
PlanNode unorderedSelect = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null,
null, validationSettings.getDataGraph(),
UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> {
return !allAllowedPredicates.contains(statement.getPredicate());
}));
// then remove any that are in the addedTargets node
PlanNode notValuesIn = new ReduceTargets(unorderedSelect, addedTargets, connectionsGroup);
// remove duplicates
PlanNode unique = Unique.getInstance(notValuesIn, false, connectionsGroup);
// then check that the rest are actually targets
PlanNode targetFilter = effectiveTarget.getTargetFilter(connectionsGroup,
validationSettings.getDataGraph(),
unique);
// this should now be targets that are not valid
PlanNode extend = effectiveTarget.extend(targetFilter, connectionsGroup,
validationSettings.getDataGraph(),
scope, EffectiveTarget.Extend.left, false, null);
targetNodePlanNode = UnionNode.getInstance(connectionsGroup, extend,
effectiveTarget.getPlanNode(connectionsGroup,
validationSettings.getDataGraph(), scope, false, null));
}
StatementMatcher.Variable<Value> predicateVariable = stableRandomVariableProvider.next();
SparqlFragment bgp = SparqlFragment.bgp(List.of(), "?a " + predicateVariable.asSparqlVariable() + " ?c.",
List.of());
String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( "
+ allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", "))
+ " ) )";
SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of());
SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter));
BulkedExternalInnerJoin bulkedExternalInnerJoin = new BulkedExternalInnerJoin(
Unique.getInstance(targetNodePlanNode, false, connectionsGroup),
connectionsGroup.getBaseConnection(),
validationSettings.getDataGraph(),
sparqlFragment,
false,
null,
(b) -> {
ValidationTuple validationTuple = new ValidationTuple(b.getValue("a"), b.getValue("c"),
Scope.propertyShape, true, validationSettings.getDataGraph());
if (produceValidationReports) {
validationTuple = validationTuple.addValidationResult(t -> {
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(),
t.getValue(),
shape,
this, shape.getSeverity(),
ConstraintComponent.Scope.nodeShape, t.getContexts(),
shape.getContexts());
validationResult.setPathIri(b.getValue(predicateVariable.getName()));
return validationResult;
});
}
return validationTuple;
},
connectionsGroup,
List.of(AbstractBulkJoinPlanNode.DEFAULT_VARS.get(0), AbstractBulkJoinPlanNode.DEFAULT_VARS.get(1),
predicateVariable)
);
return bulkedExternalInnerJoin;
}
}
@Override
public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] dataGraph, Scope scope,
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider,
ValidationSettings validationSettings) {
EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider);
switch (scope) {
case none:
throw new IllegalStateException();
case nodeShape:
BufferedSplitter targets = BufferedSplitter.getInstance(
effectiveTarget.getPlanNode(connectionsGroup, dataGraph, scope, false,
null));
// get all subjects of all triples where the predicate is not in the allAllowedPredicates set
PlanNode statementsNotMatchingPredicateList = new UnorderedSelect(connectionsGroup.getAddedStatements(),
null, null,
null, dataGraph,
UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope),
(statement -> !allAllowedPredicates.contains(statement.getPredicate())));
// then remove any that are in the targets node
statementsNotMatchingPredicateList = new ReduceTargets(statementsNotMatchingPredicateList,
targets.getPlanNode(), connectionsGroup);
// then check that the rest are actually targets
statementsNotMatchingPredicateList = effectiveTarget.getTargetFilter(connectionsGroup,
dataGraph,
statementsNotMatchingPredicateList);
if (connectionsGroup.getStats().hasRemoved()) {
// get all subjects of all triples where the predicate is not in the allAllowedPredicates set
PlanNode removed = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, null,
null, dataGraph,
UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope),
(statement -> !allAllowedPredicates.contains(statement.getPredicate())));
removed = new ReduceTargets(removed, targets.getPlanNode(), connectionsGroup);
// then check that the rest are actually targets
removed = effectiveTarget.getTargetFilter(connectionsGroup, dataGraph, removed);
statementsNotMatchingPredicateList = UnionNode.getInstance(connectionsGroup,
statementsNotMatchingPredicateList, removed);
}
// union and remove duplicates
PlanNode unique = Unique.getInstance(statementsNotMatchingPredicateList, false, connectionsGroup);
// this should now be targets that are not valid
PlanNode extend = effectiveTarget.extend(unique, connectionsGroup,
dataGraph,
scope, EffectiveTarget.Extend.left, false, null);
return extend;
case propertyShape:
Path path = getTargetChain().getPath().get();
BufferedSplitter addedTargetsBufferedSplitter = BufferedSplitter.getInstance(
effectiveTarget.getPlanNode(connectionsGroup, dataGraph, scope, false,
null));
PlanNode addedTargets = addedTargetsBufferedSplitter.getPlanNode();
PlanNode addedByPath = path.getAllAdded(connectionsGroup, dataGraph, null);
addedByPath = effectiveTarget.getTargetFilter(connectionsGroup,
dataGraph,
Unique.getInstance(new TrimToTarget(addedByPath, connectionsGroup), false, connectionsGroup));
addedByPath = new ReduceTargets(addedByPath, addedTargetsBufferedSplitter.getPlanNode(), connectionsGroup);
addedByPath = effectiveTarget.extend(addedByPath, connectionsGroup, dataGraph,
scope,
EffectiveTarget.Extend.left, false,
null);
PlanNode addedByValue = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null,
null, dataGraph,
UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> {
return !allAllowedPredicates.contains(statement.getPredicate());
}));
PlanNode removedByValue = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, null,
null, dataGraph,
UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> {
return !allAllowedPredicates.contains(statement.getPredicate());
}));
addedByValue = UnionNode.getInstance(connectionsGroup, addedByValue, removedByValue);
addedByValue = getTargetChain()
.getEffectiveTarget(Scope.nodeShape,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider)
.extend(addedByValue, connectionsGroup, dataGraph, Scope.nodeShape,
EffectiveTarget.Extend.left,
false, null);
addedByValue = getTargetChain()
.getEffectiveTarget(Scope.nodeShape,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider)
.getTargetFilter(connectionsGroup, dataGraph, addedByValue);
addedTargets = UnionNode.getInstance(connectionsGroup, addedTargets,
new TrimToTarget(new ShiftToPropertyShape(addedByValue, connectionsGroup), connectionsGroup));
addedTargets = UnionNode.getInstance(connectionsGroup, addedByPath, addedTargets);
addedTargets = Unique.getInstance(addedTargets, false, connectionsGroup);
return addedTargets;
}
throw new UnsupportedOperationException();
}
@Override
public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connectionsGroup,
ValidationSettings validationSettings, boolean negatePlan, boolean negateChildren, Scope scope) {
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider = new StatementMatcher.StableRandomVariableProvider();
EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider);
String query = effectiveTarget.getQuery(false);
StatementMatcher.Variable<Value> predicateVariable = stableRandomVariableProvider.next();
StatementMatcher.Variable<Value> objectVariable = stableRandomVariableProvider.next();
StatementMatcher.Variable<Value> value;
if (scope == Scope.nodeShape) {
value = null;
var target = effectiveTarget.getTargetVar();
query += "\n" + getFilter(target, predicateVariable, objectVariable);
} else {
value = new StatementMatcher.Variable<>("value");
SparqlFragment sparqlFragment = getTargetChain().getPath()
.map(p -> p.getTargetQueryFragment(effectiveTarget.getTargetVar(), value,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider, Set.of()))
.orElseThrow(IllegalStateException::new);
String pathQuery = sparqlFragment.getFragment();
query += "\n" + pathQuery;
query += "\n" + getFilter(value, predicateVariable, objectVariable);
}
var allTargetVariables = effectiveTarget.getAllTargetVariables();
ValidationQuery validationQuery = new ValidationQuery(getTargetChain().getNamespaces(), query,
allTargetVariables, value, scope, this,
null, null);
if (produceValidationReports) {
validationQuery = validationQuery
.withShape(shape)
.withSeverity(shape.getSeverity());
validationQuery.makeCurrentStateValidationReport();
validationQuery.setValidationResultGenerator(List.of(predicateVariable, objectVariable),
new ValidationQuery.ValidationResultGenerator() {
@Override
public Function<ValidationTuple, ValidationResult> getValidationTupleValidationResultFunction(
ValidationQuery validationQuery, Resource[] shapesGraphs, BindingSet bindings) {
Function<ValidationTuple, ValidationResult> validationResultFunction = t -> {
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(),
bindings.getValue(objectVariable.getName()), validationQuery.getShape(),
validationQuery.getConstraintComponent_validationReport(),
validationQuery.getSeverity(), t.getScope(), t.getContexts(), shapesGraphs);
validationResult.setPathIri(bindings.getValue(predicateVariable.getName()));
return validationResult;
};
return validationResultFunction;
}
});
}
return validationQuery;
}
private String getFilter(StatementMatcher.Variable<Value> target,
StatementMatcher.Variable<Value> predicateVariable, StatementMatcher.Variable<Value> objectVariable) {
SparqlFragment bgp = SparqlFragment.bgp(List.of(),
target.asSparqlVariable() + " " + predicateVariable.asSparqlVariable() + " "
+ objectVariable.asSparqlVariable() + ".",
List.of());
String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( "
+ allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", "))
+ " ) )";
SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of());
SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter));
return sparqlFragment.getFragment();
}
@Override
public ValidationApproach getOptimalBulkValidationApproach() {
return ValidationApproach.SPARQL;
}
@Override
public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope, Resource[] dataGraph,
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) {
return true;
}
@Override
public ConstraintComponent deepClone() {
return new ClosedConstraintComponent(this);
}
@Override
public List<Literal> getDefaultMessage() {
return List.of();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClosedConstraintComponent that = (ClosedConstraintComponent) o;
if (!Objects.equals(paths, that.paths)) {
return false;
}
return Objects.equals(ignoredProperties, that.ignoredProperties);
}
@Override
public int hashCode() {
int result = paths != null ? paths.hashCode() : 0;
result = 31 * result + (ignoredProperties != null ? ignoredProperties.hashCode() : 0);
return result;
}
@Override
public void setProducesValidationReport(boolean producesValidationReport) {
this.produceValidationReports = producesValidationReport;
}
@Override
public boolean producesValidationReport() {
return produceValidationReports;
}
}