/*
* Copyright (c) 2015 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.io.appschema.writer.internal;
import static eu.esdihumboldt.hale.common.align.model.functions.JoinFunction.PARAMETER_JOIN;
import static eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils.findOwningType;
import static eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils.findOwningTypePath;
import static eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils.getSortedJoinConditions;
//import static eu.esdihumboldt.hale.io.appschema.writer.internal.AppSchemaMappingUtils.findChildFeatureType;
import static eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils.getTargetProperty;
import static eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils.getTargetType;
import static eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils.isHRefAttribute;
import java.util.Collection;
import java.util.List;
import eu.esdihumboldt.cst.functions.core.Join;
import eu.esdihumboldt.hale.common.align.model.Alignment;
import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.ChildContext;
import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
import eu.esdihumboldt.hale.common.align.model.Property;
import eu.esdihumboldt.hale.common.align.model.functions.join.JoinParameter;
import eu.esdihumboldt.hale.common.align.model.functions.join.JoinParameter.JoinCondition;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AttributeExpressionMappingType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AttributeMappingType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.TypeMappingsPropertyType.FeatureTypeMapping;
import eu.esdihumboldt.hale.io.appschema.model.ChainConfiguration;
import eu.esdihumboldt.hale.io.appschema.model.FeatureChaining;
import eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils;
/**
* Translates a type cell specifying a {@link Join} transformation function to
* two app-schema feature type mappings.
*
* <p>
* The current implementation has some limitations:
* <ul>
* <li>joining more than two tables is not supported</li>
* <li>specifying more than one join condition is not supported</li>
* <li>the first join type is considered to be the container feature type; the
* second join type is considered to be the nested feature type</li>
* <li>joining non-feature types is not supported</li>
* </ul>
* </p>
*
* @author Stefano Costa, GeoSolutions
*/
public class JoinHandler implements TypeTransformationHandler {
private PropertyEntityDefinition baseProperty;
private PropertyEntityDefinition joinProperty;
/**
* @see eu.esdihumboldt.hale.io.appschema.writer.internal.TypeTransformationHandler#handleTypeTransformation(eu.esdihumboldt.hale.common.align.model.Cell,
* eu.esdihumboldt.hale.io.appschema.writer.internal.AppSchemaMappingContext)
*/
@Override
public FeatureTypeMapping handleTypeTransformation(Cell typeCell,
AppSchemaMappingContext context) {
AppSchemaMappingWrapper mapping = context.getMappingWrapper();
Alignment alignment = context.getAlignment();
FeatureChaining featureChaining = context.getFeatureChaining();
final JoinParameter joinParameter = typeCell.getTransformationParameters()
.get(PARAMETER_JOIN).get(0).as(JoinParameter.class);
String validation = joinParameter.validate();
if (validation != null)
throw new IllegalArgumentException("Join parameter invalid: " + validation);
// check only single predicate conditions have been used
int[] conditionCount = new int[joinParameter.types.size()];
List<JoinCondition> joinConditions = getSortedJoinConditions(joinParameter);
for (JoinCondition joinCondition : joinConditions) {
TypeEntityDefinition joinType = AlignmentUtil.getTypeEntity(joinCondition.joinProperty);
int typeIdx = joinParameter.types.indexOf(joinType);
conditionCount[typeIdx]++;
if (conditionCount[typeIdx] > 1) {
throw new IllegalArgumentException(
"Only single condition joins are supported so far");
}
}
FeatureTypeMapping topMostMapping = null;
for (int chainIdx = 0; chainIdx < joinParameter.types.size() - 1; chainIdx++) {
ChainConfiguration previousChainConf = null;
ChainConfiguration chainConf = null;
if (featureChaining != null) {
chainConf = featureChaining.getChain(typeCell.getId(), chainIdx);
if (chainConf != null && chainConf.getPrevChainIndex() >= 0) {
previousChainConf = featureChaining.getChain(typeCell.getId(),
chainConf.getPrevChainIndex());
}
}
// join is done pair-wise: I assume the first type is the container
// type, whereas the second type is nested in the first
JoinCondition joinCondition = joinConditions.get(chainIdx);
baseProperty = joinCondition.baseProperty;
joinProperty = joinCondition.joinProperty;
TypeEntityDefinition containerType = AlignmentUtil.getTypeEntity(baseProperty);
TypeEntityDefinition nestedType = AlignmentUtil.getTypeEntity(joinProperty);
// build FeatureTypeMapping for container type
// Entity containerTypeTarget = typeCell.getTarget().values().iterator().next();
// TypeDefinition containerTypeTargetType = containerTypeTarget.getDefinition().getType();
EntityDefinition containerTypeTarget = null;
TypeDefinition containerTypeTargetType = null;
String containerTypeTargetMappingName = null;
if (previousChainConf == null) {
containerTypeTarget = getTargetType(typeCell).getDefinition();
containerTypeTargetType = containerTypeTarget.getType();
}
else {
containerTypeTarget = previousChainConf.getNestedTypeTarget();
containerTypeTargetType = previousChainConf.getNestedTypeTarget().getDefinition()
.getPropertyType();
containerTypeTargetMappingName = previousChainConf.getMappingName();
}
String containerMappingName = null;
if (previousChainConf != null) {
containerMappingName = previousChainConf.getMappingName();
}
FeatureTypeMapping containerFTMapping = mapping.getOrCreateFeatureTypeMapping(
containerTypeTargetType, containerMappingName);
containerFTMapping
.setSourceType(containerType.getDefinition().getName().getLocalPart());
// build FeatureTypeMapping for nested type
TypeDefinition nestedFT = null;
List<ChildContext> nestedFTPath = null;
FeatureTypeMapping nestedFTMapping = null;
if (chainConf != null) {
nestedFT = chainConf.getNestedTypeTarget().getDefinition().getPropertyType();
nestedFTPath = chainConf.getNestedTypeTarget().getPropertyPath();
// remove last element
nestedFTPath = nestedFTPath.subList(0, nestedFTPath.size() - 1);
nestedFTMapping = mapping.getOrCreateFeatureTypeMapping(nestedFT,
chainConf.getMappingName());
nestedFTMapping.setSourceType(nestedType.getDefinition().getName().getLocalPart());
}
else {
if (joinParameter.types.size() > 2) {
throw new IllegalArgumentException(
"If no feature chaining configuration is provided, only join between 2 types is supported");
}
// do your best to figure it out on your own... good luck!
Collection<? extends Cell> propertyCells = alignment.getPropertyCells(typeCell);
for (Cell propertyCell : propertyCells) {
Property sourceProperty = AppSchemaMappingUtils.getSourceProperty(propertyCell);
if (sourceProperty != null) {
TypeDefinition sourceType = sourceProperty.getDefinition().getDefinition()
.getParentType();
if (sourceType.getName().equals(nestedType.getDefinition().getName())) {
// source property belongs to nested type: determine
// target type
Property targetProperty = getTargetProperty(propertyCell);
// nestedFT =
// findOwningFeatureType(targetProperty.getDefinition());
nestedFT = findOwningType(targetProperty.getDefinition(),
context.getRelevantTargetTypes());
if (nestedFT != null
&& !nestedFT.getName()
.equals(containerTypeTargetType.getName())) {
// target property belongs to a feature type
// different from the already mapped one: build
// a new mapping
nestedFTPath = findOwningTypePath(targetProperty.getDefinition(),
context.getRelevantTargetTypes());
// TODO: always generate unique mapping name?
nestedFTMapping = mapping.getOrCreateFeatureTypeMapping(nestedFT);
nestedFTMapping.setSourceType(nestedType.getDefinition().getName()
.getLocalPart());
// I assume at most 2 FeatureTypes are involved
// in the join
break;
}
else if (isHRefAttribute(targetProperty.getDefinition().getDefinition())) {
// check if target property is a href attribute
Property hrefProperty = targetProperty;
List<ChildContext> hrefPropertyPath = hrefProperty.getDefinition()
.getPropertyPath();
List<ChildContext> hrefContainerPath = hrefPropertyPath.subList(0,
hrefPropertyPath.size() - 1);
TypeDefinition hrefParentType = hrefProperty.getDefinition()
.getDefinition().getParentType();
// TypeDefinition childFT =
// findChildFeatureType(hrefParentType);
TypeDefinition childFT = AppSchemaMappingUtils.findChildType(
hrefParentType, context.getRelevantTargetTypes());
if (childFT != null) {
nestedFTPath = hrefContainerPath;
// TODO: always generate unique mapping
// name?
nestedFTMapping = mapping
.getOrCreateFeatureTypeMapping(childFT);
nestedFTMapping.setSourceType(nestedType.getDefinition()
.getName().getLocalPart());
// I assume at most 2 FeatureTypes are
// involved in the join
break;
}
}
}
}
}
}
// build join mapping
if (nestedFTMapping != null && nestedFTPath != null) {
AttributeMappingType containerJoinMapping = mapping.getOrCreateAttributeMapping(
containerTypeTargetType, containerTypeTargetMappingName, nestedFTPath);
containerJoinMapping.setTargetAttribute(mapping.buildAttributeXPath(
containerTypeTargetType, nestedFTPath));
// set isMultiple attribute
PropertyDefinition targetPropertyDef = nestedFTPath.get(nestedFTPath.size() - 1)
.getChild().asProperty();
if (AppSchemaMappingUtils.isMultiple(targetPropertyDef)) {
containerJoinMapping.setIsMultiple(true);
}
AttributeExpressionMappingType containerSourceExpr = new AttributeExpressionMappingType();
// join column extracted from join condition
containerSourceExpr.setOCQL(baseProperty.getDefinition().getName().getLocalPart());
containerSourceExpr.setLinkElement(getLinkElementValue(nestedFTMapping));
String linkField = mapping.getUniqueFeatureLinkAttribute(nestedFT,
nestedFTMapping.getMappingName());
containerSourceExpr.setLinkField(linkField);
containerJoinMapping.setSourceExpression(containerSourceExpr);
AttributeMappingType nestedJoinMapping = new AttributeMappingType();
AttributeExpressionMappingType nestedSourceExpr = new AttributeExpressionMappingType();
// join column extracted from join condition
nestedSourceExpr.setOCQL(joinProperty.getDefinition().getName().getLocalPart());
nestedJoinMapping.setSourceExpression(nestedSourceExpr);
nestedJoinMapping.setTargetAttribute(linkField);
nestedFTMapping.getAttributeMappings().getAttributeMapping().add(nestedJoinMapping);
}
if (chainIdx == 0) {
topMostMapping = containerFTMapping;
}
}
return topMostMapping;
}
private String getLinkElementValue(FeatureTypeMapping nestedFeatureTypeMapping) {
if (nestedFeatureTypeMapping.getMappingName() != null
&& !nestedFeatureTypeMapping.getMappingName().isEmpty()) {
// playing safe: always enclose mapping name in single quotes
return "'" + nestedFeatureTypeMapping.getMappingName() + "'";
}
else {
return nestedFeatureTypeMapping.getTargetElement();
}
}
}