/* * Copyright (c) 2017 wetransform GmbH * * 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: * wetransform GmbH <http://www.wetransform.to> */ package eu.esdihumboldt.cst.functions.collector; import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import com.google.common.collect.ListMultimap; import eu.esdihumboldt.cst.MultiValue; import eu.esdihumboldt.cst.functions.collector.helpers.CollectorGroovyHelper; import eu.esdihumboldt.cst.functions.groovy.helpers.ContextHelpers; import eu.esdihumboldt.cst.functions.groovy.helpers.util.Collector; import eu.esdihumboldt.hale.common.align.model.ParameterValue; import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition; import eu.esdihumboldt.hale.common.align.transformation.engine.TransformationEngine; import eu.esdihumboldt.hale.common.align.transformation.function.PropertyValue; import eu.esdihumboldt.hale.common.align.transformation.function.TransformationException; import eu.esdihumboldt.hale.common.align.transformation.function.impl.AbstractSingleTargetPropertyTransformation; import eu.esdihumboldt.hale.common.align.transformation.function.impl.NoResultException; import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog; import eu.esdihumboldt.hale.common.align.transformation.report.impl.TransformationMessageImpl; import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil; import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.common.schema.model.constraint.property.Reference; import eu.esdihumboldt.hale.common.schema.model.constraint.type.HasValueFlag; /** * Function to assign values collected by a {@link Collector} to a multi-valued * target property. In case the target property has the {@link Reference} * constraint, the values are passed to {@link Reference#idToReference(Object)} * before assignment. * * If the target property cannot take the values itself, a child property with * the {@link Reference} constraint is looked up to assign the values to. * * @author Florian Esser */ public class AssignFromCollector extends AbstractSingleTargetPropertyTransformation<TransformationEngine> implements AssignFromCollectorFunction { private static final CollectorGroovyHelper helper = new CollectorGroovyHelper(); /** * @see eu.esdihumboldt.hale.common.align.transformation.function.impl.AbstractSingleTargetPropertyTransformation#evaluate(java.lang.String, * eu.esdihumboldt.hale.common.align.transformation.engine.TransformationEngine, * com.google.common.collect.ListMultimap, java.lang.String, * eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition, * java.util.Map, * eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog) */ @Override protected Object evaluate(String transformationIdentifier, TransformationEngine engine, ListMultimap<String, PropertyValue> variables, String resultName, PropertyEntityDefinition resultProperty, Map<String, String> executionParameters, TransformationLog log) throws TransformationException, NoResultException { // XXX check anchor? final Collector mainCollector = (Collector) getExecutionContext().getTransformationContext() .get(ContextHelpers.KEY_COLLECTOR); if (mainCollector == null) { throw new TransformationException( "Fatal: No collector has been created yet. Check function priority."); } final ParameterValue collectorName = getParameterChecked(PARAMETER_COLLECTOR); if (collectorName == null || collectorName.isEmpty()) { throw new TransformationException("Fatal: No collector name was specified."); } final Collector collector = mainCollector.getAt(collectorName.getValue().toString()); if (collector == null) { throw new TransformationException(MessageFormat.format( "Error retrieving collector \"{0}\"", collectorName.getValue().toString())); } else if (collector.values().isEmpty()) { log.warn(new TransformationMessageImpl(getCell(), MessageFormat.format( "Collector \"{0}\" contains no values. If this is unexpected, check the spelling of the collector name and the priority of the transformation function.", collectorName.getStringRepresentation()), null)); } // Determine where to assign the collected values final TypeDefinition resultPropertyType = resultProperty.getDefinition().getPropertyType(); final PropertyDefinition targetProperty; final ResultStrategy resultStrategy; if (resultPropertyType.getConstraint(HasValueFlag.class).isEnabled()) { // The result property can take values, therefore assign directly to // property targetProperty = resultProperty.getDefinition(); // No instance creation is required in this case resultStrategy = ResultStrategy.USE_VALUE; } else { // Find child element/attribute that can be assigned the reference targetProperty = Optional.ofNullable(findReferenceChildProperty(resultPropertyType)) .orElseThrow(() -> new TransformationException( "Fatal: No child property could be found to assign a reference to.")); resultStrategy = ResultStrategy.BUILD_INSTANCE; } List<Object> collectedReferences = helper.extractCollectedValues(collector); // Process collected values if target property is a reference, otherwise // use plain values final Function<Object, Object> referenceStrategy; if (targetProperty.getConstraint(Reference.class).isReference()) { final Reference referenceConstraint = targetProperty.getConstraint(Reference.class); // Use the idToReference method to construct the reference referenceStrategy = referenceConstraint::idToReference; } else { referenceStrategy = Function.identity(); } MultiValue result = new MultiValue(); collectedReferences.forEach(ref -> result.add(resultStrategy .createResult(resultPropertyType, targetProperty, referenceStrategy.apply(ref)))); return result; } /** * Find first child property of the given {@link TypeDefinition} where * <code>child.getConstraint(Reference.class).isReference()</code> returns * true. * * @param propertyType The <code>TypeDefinition</code> to search * * @return The first child that matches or <code>null</code> if none match */ private PropertyDefinition findReferenceChildProperty(TypeDefinition propertyType) { return DefinitionUtil.getAllProperties(propertyType).stream() .filter(prop -> prop.getConstraint(Reference.class).isReference()).findFirst() .orElse(null); } private interface ResultStrategy { public final ResultStrategy USE_VALUE = new ResultStrategy() { @Override public Object createResult(TypeDefinition type, PropertyDefinition property, Object value) { return value; } }; public final ResultStrategy BUILD_INSTANCE = new ResultStrategy() { @Override public Object createResult(TypeDefinition type, PropertyDefinition property, Object value) { return helper.createInstance(type, property, value); } }; Object createResult(TypeDefinition type, PropertyDefinition property, Object value); } }