io.konig.cadl.CubeShapeBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of konig-core Show documentation
Show all versions of konig-core Show documentation
A library for core classes (Graph, Vertex, Edge, etc.)
package io.konig.cadl;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/*
* #%L
* Konig Core
* %%
* Copyright (C) 2015 - 2019 Gregory McFall
* %%
* Licensed 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.
* #L%
*/
import java.util.Set;
import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.konig.core.OwlReasoner;
import io.konig.core.impl.RdfUtil;
import io.konig.core.showl.ShowlClass;
import io.konig.core.showl.ShowlManager;
import io.konig.core.showl.ShowlProperty;
import io.konig.core.showl.ShowlPropertyShape;
import io.konig.core.showl.ShowlTraverser;
import io.konig.datasource.DataSource;
import io.konig.formula.DirectionStep;
import io.konig.formula.Formula;
import io.konig.formula.FormulaVisitor;
import io.konig.formula.FunctionExpression;
import io.konig.formula.PathExpression;
import io.konig.formula.PathStep;
import io.konig.formula.PathTerm;
import io.konig.formula.PrimaryExpression;
import io.konig.formula.QuantifiedExpression;
import io.konig.formula.VariableTerm;
import io.konig.shacl.NodeKind;
import io.konig.shacl.PropertyConstraint;
import io.konig.shacl.Shape;
import io.konig.shacl.ShapeManager;
public class CubeShapeBuilder {
private static Logger logger = LoggerFactory.getLogger(CubeShapeBuilder.class);
private ShapeManager shapeManager;
private OwlReasoner reasoner;
private String shapeNamespace;
public CubeShapeBuilder(OwlReasoner reasoner, ShapeManager shapeManager, String shapeNamespace) {
this.reasoner = reasoner;
this.shapeManager = shapeManager;
this.shapeNamespace = shapeNamespace;
}
public Shape buildShape(Cube cube) throws CubeShapeException {
Worker worker = new Worker();
return worker.buildShape(cube);
}
private class Worker {
private ShowlTraverser traverser;
private PropertyConstraint sourceVariable;
private String sourceVarName;
public Shape buildShape(Cube cube) throws CubeShapeException {
if (logger.isDebugEnabled()) {
logger.debug("buildShape({})", cube.getId());
}
ShowlManager showlManager = new ShowlManager(shapeManager, reasoner);
traverser = new ShowlTraverser(showlManager);
URI shapeId = uri(shapeNamespace + cube.getId().getLocalName() + "RawShape");
Shape shape = produceShape(shapeId);
shape.setNodeShapeCube(cube);
addVariable(shape, cube, cube.getSource());
List location = new ArrayList<>();
for (Dimension dim : cube.getDimension()) {
location.clear();
rewriteFormula(location, dim.getFormula());
for (Level level : dim.getLevel()) {
addLevel(location, shape, cube, dim, level);
}
}
addDataSources(shape, cube);
return shape;
}
private void addDataSources(Shape shape, Cube cube) {
for (DataSource ds : cube.getStorage()) {
shape.addShapeDataSource(ds);
}
}
private void addVariable(Shape shape, Cube cube, Variable source) {
PropertyConstraint p = shape.getVariableById(source.getId());
if (p == null) {
URI predicate = CubeUtil.predicate(cube, source.getId());
p = new PropertyConstraint(predicate);
shape.addVariable(p);
if (logger.isTraceEnabled()) {
logger.trace("addVariable: {}", p.getPredicate().stringValue());
}
}
p.setValueClass(source.getValueType());
sourceVariable = p;
sourceVarName = "?" + p.getPredicate().getLocalName();
shape.setTargetClass(targetClass(cube));
if (logger.isTraceEnabled()) {
logger.trace("addVariable: On {}, targetClass = {}", RdfUtil.localName(shape.getId()), RdfUtil.localName(shape.getTargetClass()));
}
}
private URI targetClass(Cube cube) {
return RdfUtil.uri(sourceVariable.getValueClass());
}
private void addLevel(List location, Shape shape, Cube cube, Dimension dim, Level level) throws CubeShapeException {
if (logger.isTraceEnabled()) {
logger.trace("addLevel(shape: {}, level: {})", RdfUtil.localName(shape.getId()), RdfUtil.localName(level.getId()));
}
URI predicate = CubeUtil.predicate(cube, level.getId());
PropertyConstraint p = shape.getPropertyConstraint(predicate);
if (p == null) {
p = new PropertyConstraint(predicate);
shape.add(p);
}
p.setMinCount(0);
p.setMaxCount(1);
setLevelFormula(location, dim, level, p);
if (!level.getAttribute().isEmpty()) {
setLevelValueClass(location, dim, level, p);
URI levelShapeId = uri(shapeNamespace + cube.getId().getLocalName() + "RawShape/level/" + level.getId().getLocalName() + "Shape");
Shape levelShape = produceShape(levelShapeId);
p.setShape(levelShape);
addAttributes(levelShape, cube, level, p, location);
} else {
setDatatype(p, p.getFormula(), level.getId());
}
}
private void setLevelValueClass(List location, Dimension dim, Level level, PropertyConstraint p) throws CubeShapeException {
QuantifiedExpression formula = p.getFormula();
Set valueClassCandidates = traverser.valueClass(RdfUtil.uri(sourceVariable.getValueClass()), formula);
if (valueClassCandidates.size()==1) {
p.setValueClass(valueClassCandidates.iterator().next());
if (logger.isTraceEnabled()) {
logger.trace("setLevelValueClass: level={}, valueClass={}", level.getId().getLocalName(), RdfUtil.localName(p.getValueClass()));
}
} else {
throw new CubeShapeException("Failed to determine valueClass for level " + level.getId().stringValue());
}
}
private void addAttributes(Shape shape, Cube cube, Level level, PropertyConstraint levelConstraint, List location) throws CubeShapeException {
if (logger.isTraceEnabled()) {
logger.debug("addAttributes(shape: {}, level: {})", RdfUtil.localName(shape.getId()), level.getId().getLocalName());
}
URI datatype=null;
for (Attribute attr : level.getAttribute()) {
if (attr.getId().getLocalName().equals("id")) {
shape.setNodeKind(NodeKind.IRI);
continue;
}
URI predicate = null;
if (attr.getFormula()==null) {
// The attribute does not declare a formula.
// This implies that there must be a sh:Nodeshape with the same target class that has
// a property with the same local name as the attribute.
//
// We now lookup that property and create a formulat that references it.
URI levelClass = RdfUtil.uri(levelConstraint.getValueClass());
if (levelClass == null) {
throw new CubeShapeException("Target class not defined for level: " + level.getId().stringValue());
}
String localName = attr.getId().getLocalName();
ShowlPropertyShape property = traverser.findPropertyByLocalName(levelClass, localName);
if (property == null) {
throw new CubeShapeException("Property not found for attribute: " + attr.getId().stringValue());
}
predicate = property.getPredicate();
datatype = datatype(property);
if (logger.isTraceEnabled()) {
logger.trace("addAttributes: At level {}, added direct attribute {}", level.getId().getLocalName(), predicate.stringValue());
}
} else {
predicate = CubeUtil.predicate(cube, attr.getId());
}
PropertyConstraint p = shape.getPropertyConstraint(predicate);
if (p == null) {
p = new PropertyConstraint(predicate);
shape.add(p);
}
QuantifiedExpression formula = rewriteFormula(location, attr.getFormula());
if (logger.isTraceEnabled() && formula!=null) {
logger.trace("addAttributes: level={}, attribute={}, formula={}",
level.getId().getLocalName(),
attr.getId().getLocalName(),
formula.toSimpleString());
}
p.setFormula(formula);
p.setMinCount(0);
p.setMaxCount(1);
if (datatype != null) {
p.setDatatype(datatype);
}
if (p.getDatatype() == null) {
setDatatype(p, formula, attr.getId());
}
}
}
private URI datatype(ShowlPropertyShape property) {
PropertyConstraint constraint = property.getPropertyConstraint();
if (constraint != null && constraint.getDatatype()!=null) {
return constraint.getDatatype();
}
ShowlProperty p = property.getProperty();
if (p!=null) {
if (p.getRange() != null) {
return p.getRange().getId();
}
ShowlClass owlClass = p.inferRange(traverser.getManager().getShowlFactory());
if (owlClass != null) {
return owlClass.getId();
}
}
return null;
}
/**
* Rewrite the formula to eliminate references to the variable.
*
* For example, suppose we have the following structure:
*
*
*
* VARIABLE ?opportunity
* DIMENSION AccountDim <==> ?opportunity.customerAccount
* LEVEL industry <==> ?opportunity.customerAccount.industry
* ATTRIBUTE industryCode <==> ?opportunity.customerAccount.industry.identifier
*
* At level 'industry' we rewrite the formula as '$.customerAccount.industry'
* At attribute 'industryCode' we rewrite the formula as '$.identifier' since it has the same base as the parent level.
*
* If the given formula does not start with a variable, we return the formula as-is.
* If the given location is empty, we remove the leading variable step, and populate the location with the current path.
* If the given location is non-empty and the given formula starts with a variable, we remove the leading path.
*
*
* @param location
* @param formula
* @return
*/
private QuantifiedExpression rewriteFormula(List location, QuantifiedExpression formula) {
if (formula != null) {
String oldFormulaText = null;
if (logger.isTraceEnabled()) {
oldFormulaText = formula.toSimpleString();
}
formula = formula.clone();
// Collect the list of PathExpression instances within the formula
final List pathList = new ArrayList<>();
formula.dispatch(new FormulaVisitor() {
@Override
public void enter(Formula formula) {
if (formula instanceof PathExpression) {
pathList.add((PathExpression) formula);
}
}
@Override
public void exit(Formula formula) {
}
});
// For each PathExpression that we discovered, apply the rewrite rules
for (PathExpression path : pathList) {
if (startsWithVariable(path)) {
List stepList = path.getStepList();
if (location.isEmpty()) {
stepList.remove(0);
location.addAll(stepList);
} else {
// TODO: Verify that each element of 'stepList' is equivalent to the
// corresponding element of 'location'
// For now, we just assume the equivalence.
for (int i=0; i {}", oldFormulaText, formula.toSimpleString());
}
}
return formula;
}
private boolean startsWithVariable(PathExpression path) {
List stepList = path.getStepList();
if (!stepList.isEmpty()) {
PathStep step = stepList.get(0);
if (step instanceof DirectionStep) {
DirectionStep dirStep = (DirectionStep) step;
PathTerm term = dirStep.getTerm();
return term instanceof VariableTerm;
}
}
return false;
}
private void setDatatype(PropertyConstraint p, QuantifiedExpression formula, URI elementId) throws CubeShapeException {
if (formula == null) {
throw new CubeShapeException(
MessageFormat.format("Formula must be defined for <{0}>", elementId.stringValue())
);
}
PrimaryExpression primary = formula.asPrimaryExpression();
if (primary instanceof FunctionExpression) {
FunctionExpression func = (FunctionExpression) primary;
URI datatype = func.getModel().getReturnType().getRdfType();
p.setDatatype(datatype);
if (logger.isTraceEnabled()) {
logger.trace("setDatatype: type={} at {}", datatype.getLocalName(), elementId.stringValue());
}
return;
}
Set propertySet =
traverser.traverse(sourceVariable.getPredicate(), RdfUtil.uri(sourceVariable.getValueClass()), formula);
if (propertySet.size()==1) {
setDatatype(propertySet.iterator().next(), p, elementId, formula);
} else if (propertySet.isEmpty()) {
throw new CubeShapeException(
MessageFormat.format("Datatype not found for <{0}> mapped by {1}", elementId.stringValue(), formula.toSimpleString())
);
} else {
// propertySet contains multiple properties
Set datatypeSet = new HashSet<>();
for (ShowlProperty property : propertySet) {
datatypeSet.addAll(property.rangeIncludes(reasoner));
}
if (datatypeSet.size() == 1) {
URI datatype = datatypeSet.iterator().next();
if (reasoner.isDatatype(datatype)) {
p.setDatatype(datatype);
if (logger.isTraceEnabled()) {
logger.trace("setDatatype: type={} at {}", datatype.getLocalName(), elementId.stringValue());
}
} else {
throw new CubeShapeException(
MessageFormat.format(
"<{0}> must have a Datatype value, but the formula {1} implies <{2}>",
elementId.stringValue(),
formula.toSimpleString(),
datatype.stringValue())
);
}
} else if (datatypeSet.isEmpty()) {
throw new CubeShapeException(
MessageFormat.format(
"Datatype for <{0}> is not known. The datatype must be specified explicitly.",
elementId.stringValue()));
} else {
StringBuilder msg = new StringBuilder();
msg.append("Datatype is ambiguous for <");
msg.append(elementId.stringValue());
msg.append(">. The datatype must be specified explicitly.");
throw new CubeShapeException(msg.toString());
}
}
}
private void setDatatype(ShowlProperty property, PropertyConstraint p, URI element, QuantifiedExpression formula) throws CubeShapeException {
ShowlClass range = property.getRange();
URI datatype = null;
if (range == null) {
Set set = property.rangeIncludes(reasoner);
if (set.size()==1) {
datatype = set.iterator().next();
} else if (set.size()>1) {
StringBuilder msg = new StringBuilder();
msg.append("Datatype is ambiguous for <");
msg.append(element.stringValue());
msg.append(">. The datatype must be specified explicitly.");
throw new CubeShapeException(msg.toString());
}
} else {
datatype = range.getId();
}
if (datatype == null) {
throw new CubeShapeException(
MessageFormat.format(
"Datatype for <{0}> is not known. The datatype must be specified explicitly.",
element.stringValue()));
}
if (!reasoner.isDatatype(datatype)) {
throw new CubeShapeException(
MessageFormat.format(
"<{0}> must have a Datatype value, but the formula {1} implies <{2}>",
element.stringValue(),
formula.toSimpleString(),
datatype.stringValue())
);
}
p.setDatatype(datatype);
}
private Shape produceShape(URI shapeId) {
Shape shape = shapeManager.getShapeById(shapeId);
if (shape == null) {
shape = new Shape(shapeId);
shapeManager.addShape(shape);
}
return shape;
}
private void setLevelFormula(List location, Dimension dim, Level level, PropertyConstraint p) {
QuantifiedExpression formula = rewriteFormula(location, level.getFormula());
p.setFormula(formula);
if (formula!=null && logger.isTraceEnabled()) {
logger.trace("At Dimension {}, Level {}... formula = {}",
dim.getId().getLocalName(), level.getId().getLocalName(), formula.toSimpleString());
}
}
private URI uri(String value) {
return new URIImpl(value);
}
}
}