package nl.ipo.cds.etl.attributemapping; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import nl.idgis.commons.jobexecutor.Job; import nl.idgis.commons.jobexecutor.JobLogger.LogLevel; import nl.ipo.cds.attributemapping.AttributeMapperUtils; import nl.ipo.cds.attributemapping.operations.Operation; import nl.ipo.cds.attributemapping.operations.OperationInput; import nl.ipo.cds.attributemapping.operations.OperationInputType; import nl.ipo.cds.attributemapping.operations.OperationType; import nl.ipo.cds.dao.attributemapping.InputOperationDTO; import nl.ipo.cds.dao.attributemapping.OperationDTO; import nl.ipo.cds.dao.attributemapping.OperationInputDTO; import nl.ipo.cds.dao.attributemapping.TransformOperationDTO; import nl.ipo.cds.domain.FeatureType; import nl.ipo.cds.domain.FeatureTypeAttribute; import nl.ipo.cds.etl.log.EventLogger; import nl.ipo.cds.etl.operations.transform.ConditionalTransform; import nl.ipo.cds.etl.theme.AttributeDescriptor; public class AttributeMappingValidator { public enum MessageKey { // Attribute mapping is incorrect, but due to a technical error: ATTRIBUTE_MAPPING_TECHNICAL_ERROR, ATTRIBUTE_MAPPING_MISSING, ATTRIBUTE_MAPPING_CONDITION_INVALID_ATTRIBUTE, ATTRIBUTE_MAPPING_CONDITION_NO_VALUE, ATTRIBUTE_MAPPING_TYPES_INCOMPATIBLE, ATTRIBUTE_MAPPING_INPUT_COUNT_INVALID, ATTRIBUTE_MAPPING_INPUT_NOT_FOUND, ATTRIBUTE_MAPPING_INPUT_NO_LONGER_AVAILABLE, ATTRIBUTE_MAPPING_INPUT_TYPE_CHANGED, ATTRIBUTE_MAPPING_RUNTIME_ERROR } private class Logger { private int messageCount = 0; private final Job job; public Logger (final Job job) { this.job = job; } public void report (final MessageKey messageKey, final String ... parameters) { final List<String> params = new ArrayList<String> (); final Map<String, Object> context = new HashMap<String, Object> (); params.add (attributeDescriptor.getLabel (Locale.getDefault ())); params.addAll (Arrays.asList (parameters)); context.put ("attribute", attributeDescriptor.getName ()); context.put ("attributeLabel", attributeDescriptor.getLabel (Locale.getDefault ())); logger.logEvent (job, messageKey, LogLevel.ERROR, context, params.toArray (new String[params.size ()])); ++ messageCount; } public int getMessageCount () { return messageCount; } } private final EventLogger<MessageKey> logger; private final AttributeDescriptor<?> attributeDescriptor; private final FeatureType featureType; public AttributeMappingValidator (final AttributeDescriptor<?> attributeDescriptor, final FeatureType featureType, final EventLogger<MessageKey> logger) { this.logger = logger; this.attributeDescriptor = attributeDescriptor; this.featureType = featureType; } public boolean isValid (final Job job, final OperationDTO rootOperation) { final Logger logger = new Logger (job); validateRoot (rootOperation, logger); return logger.getMessageCount () == 0; } public void validateRoot (final OperationDTO rootOperation, final Logger logger) { if (rootOperation == null) { logger.report (MessageKey.ATTRIBUTE_MAPPING_MISSING); return; } // Must be an instance of TransformOperationDTO: if (!(rootOperation instanceof TransformOperationDTO)) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, "Root operation must be an instance of TransformOperationDTO"); return; } // Validate the operation type: if (!validateOperationType (rootOperation, logger)) { return; } // Root must be an attribute descriptor: if (!(rootOperation instanceof TransformOperationDTO)) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, "Root operation must be of type ConditionalTransform"); return; } // Must be of type attribute descriptor: final TransformOperationDTO operation = (TransformOperationDTO)rootOperation; if (!ConditionalTransform.Settings.class.equals (operation.getOperationType ().getPropertyBeanClass ())) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, "Root operation must be of type ConditionalTransform"); return; } // Test conditions: final ConditionalTransform.Settings settings = (ConditionalTransform.Settings)operation.getOperationProperties (); final List<ConditionalTransform.Condition> conditions = settings.getConditions (); final List<OperationInput> inputs = operation.getInputs (); if (!(conditions.size () == 0 && inputs.size () == 0) && conditions.size () != inputs.size () - 1) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, String.format ("Invalid number of conditions %d for root operation, expected %d", conditions.size (), inputs.size () - 1)); } for (int i = 0; i < conditions.size (); ++ i) { final ConditionalTransform.Condition condition = conditions.get (i); if (condition.getAttribute () == null || !hasFeatureTypeAttribute (condition.getAttribute ())) { logger.report (MessageKey.ATTRIBUTE_MAPPING_CONDITION_INVALID_ATTRIBUTE, String.valueOf (i + 1), condition.getAttribute () == null ? "" : condition.getAttribute ()); } if ((condition.getOperation () == ConditionalTransform.Operation.IN || condition.getOperation () == ConditionalTransform.Operation.NOT_IN) && (condition.getValues () == null || condition.getValues ().length == 0)) { logger.report (MessageKey.ATTRIBUTE_MAPPING_CONDITION_NO_VALUE, String.valueOf (i + 1)); } } for (int i = 0; i < inputs.size (); ++ i) { validateInput (inputs.get (i), operation, i, logger); // Test whether the inputs of the condition are compatible with the result attribute: if (inputs.get (i) != null && inputs.get (i).getOperation () != null && inputs.get (i).getOperation ().getOperationType () != null) { if (!AttributeMapperUtils.areTypesAssignable (inputs.get (i).getOperation ().getOperationType ().getReturnType (), attributeDescriptor.getAttributeType ())) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TYPES_INCOMPATIBLE); } } } } public void validateInput (final OperationInput operationInput, final TransformOperationDTO parent, final int index, final Logger logger) { if (operationInput == null || !(operationInput instanceof OperationInputDTO)) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, "Invalid operation input type"); return; } final OperationInputDTO input = (OperationInputDTO)operationInput; final Operation operation = input.getOperation (); if (operation == null) { logger.report (MessageKey.ATTRIBUTE_MAPPING_INPUT_COUNT_INVALID); return; } if (!(operation instanceof OperationDTO)) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, String.format ("Invalid operation instance type %s, expected %s", operation == null ? "NULL" : operation.getClass ().getCanonicalName (), OperationDTO.class.getCanonicalName ())); return; } validateOperation ((OperationDTO)operation, parent, index, logger); } public void validateOperation (final OperationDTO operation, final TransformOperationDTO parent, final int index, final Logger logger) { // Validate the operation type: if (!validateOperationType (operation, logger)) { return; } // Check whether the operation is assignable to the parent: final List<OperationInputType> inputTypes = parent.getOperationType ().getInputs (); final Type inputType = inputTypes.get (Math.min (index, inputTypes.size () - 1)).getInputType (); final OperationType operationType = operation.getOperationType (); if (operationType == null || !AttributeMapperUtils.areTypesAssignable (operationType.getReturnType (), inputType)) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TYPES_INCOMPATIBLE); } if (operation instanceof InputOperationDTO) { validateInputOperation ((InputOperationDTO)operation, parent, index, logger); } else if (operation instanceof TransformOperationDTO) { validateTransformOperation ((TransformOperationDTO)operation, parent, index, logger); } else { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, "Wrong operation type"); } } public void validateInputOperation (final InputOperationDTO operation, final TransformOperationDTO parent, final int index, final Logger logger) { // Test whether the attribute is still valid: if (operation.getAttribute () == null) { logger.report(MessageKey.ATTRIBUTE_MAPPING_INPUT_NOT_FOUND, operation.getAttributeName ()); return; } final FeatureTypeAttribute attribute = operation.getAttribute (); if (!attribute.getName ().getLocalPart ().equals (operation.getAttributeName ())) { logger.report (MessageKey.ATTRIBUTE_MAPPING_INPUT_NO_LONGER_AVAILABLE, operation.getAttributeName ()); } if (!attribute.getType ().equals (operation.getAttributeType ())) { logger.report (MessageKey.ATTRIBUTE_MAPPING_INPUT_TYPE_CHANGED, operation.getAttributeName ()); } } public void validateTransformOperation (final TransformOperationDTO operation, final TransformOperationDTO parent, final int index, final Logger logger) { final List<OperationInput> inputs = operation.getInputs (); final OperationType operationType = operation.getOperationType (); final List<OperationInputType> inputTypes = operationType.getInputs (); final boolean hasVariableInputs = inputTypes.size () == 0 ? false : inputTypes.get (inputTypes.size () - 1).isVariableInputCount (); if (hasVariableInputs) { if (inputs.size () < inputTypes.size () - 1) { logger.report (MessageKey.ATTRIBUTE_MAPPING_INPUT_COUNT_INVALID); return; } } else { if (inputs.size () != inputTypes.size ()) { logger.report (MessageKey.ATTRIBUTE_MAPPING_INPUT_COUNT_INVALID); } } for (int i = 0; i < inputs.size (); ++ i) { if (inputs.get (i) == null || inputs.get (i).getOperation () == null) { logger.report (MessageKey.ATTRIBUTE_MAPPING_INPUT_COUNT_INVALID); break; } validateInput (inputs.get (i), operation, i, logger); } } public boolean validateOperationType (final OperationDTO operation, final Logger logger) { if (operation.getOperationType () == null) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, String.format ("Operation %s has no operation type", operation)); return false; } if (operation.getOperationType ().getPropertyBeanClass () != null) { if (operation.getOperationProperties () == null) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, String.format ("Operation %s has no properties object", operation)); return false; } if (!operation.getOperationProperties ().getClass ().equals (operation.getOperationType ().getPropertyBeanClass ())) { logger.report (MessageKey.ATTRIBUTE_MAPPING_TECHNICAL_ERROR, String.format ("Operation %s must have a properties object of type %s", operation, operation.getOperationType ().getPropertyBeanClass ().getCanonicalName ())); return false; } } return true; } private boolean hasFeatureTypeAttribute (final String name) { for (final FeatureTypeAttribute attr: featureType.getAttributes ()) { if (attr.getName ().getLocalPart ().equals (name)) { return true; } } return false; } }