package nl.ipo.cds.dao.attributemapping;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import nl.ipo.cds.attributemapping.operations.OperationInput;
import nl.ipo.cds.attributemapping.operations.OperationType;
import nl.ipo.cds.attributemapping.operations.OutputOperationType;
import nl.ipo.cds.dao.ManagerDao;
import nl.ipo.cds.domain.AttributeMapping;
import nl.ipo.cds.domain.AttributeType;
import nl.ipo.cds.domain.Dataset;
import nl.ipo.cds.domain.FeatureTypeAttribute;
import nl.ipo.cds.domain.MappingOperation;
import nl.ipo.cds.domain.MappingOperation.MappingOperationType;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.transaction.annotation.Transactional;
public class AttributeMappingDao {
private final ManagerDao managerDao;
private final List<FeatureTypeAttribute> inputAttributes;
private final List<OperationType> operationTypes;
public AttributeMappingDao (final ManagerDao managerDao) {
this (managerDao, new ArrayList<FeatureTypeAttribute> (), new ArrayList<OperationType> ());
}
public AttributeMappingDao (final ManagerDao managerDao, final Collection<OperationType> operationTypes) {
this (managerDao, new ArrayList<FeatureTypeAttribute> (), operationTypes);
}
public AttributeMappingDao (final ManagerDao managerDao, final Collection<FeatureTypeAttribute> inputAttributes, final Collection<OperationType> operationTypes) {
this.managerDao = managerDao;
this.inputAttributes = new ArrayList<FeatureTypeAttribute> (inputAttributes);
this.operationTypes = new ArrayList<OperationType> (operationTypes);
}
@Transactional
public OperationDTO getAttributeMapping (final Dataset dataset, final OutputOperationType attribute) {
final AttributeMapping attributeMapping = managerDao.getAttributeMapping (dataset, attribute.getName ());
// Attribute mapping hasn't been persisted yet:
if (attributeMapping == null || attributeMapping.getRootOperation () == null) {
return null;
}
return unmarshalOperation (attributeMapping.getRootOperation ());
}
private OperationDTO unmarshalOperation (final MappingOperation mappingOperation) {
if (mappingOperation == null) {
return null;
} else if (mappingOperation.getOperationType () == MappingOperationType.INPUT_OPERATION) {
return unmarshalInputOperation (mappingOperation);
} else if (mappingOperation.getOperationType () == MappingOperationType.TRANSFORM_OPERATION) {
return unmarshalTransformOperation (mappingOperation);
} else {
throw new IllegalArgumentException (String.format ("Mapping operation has an invalid type: %s", mappingOperation.getOperationType ()));
}
}
private InputOperationDTO unmarshalInputOperation (final MappingOperation mappingOperation) {
if (mappingOperation == null) {
throw new NullPointerException ("mappingOperation cannot be null");
}
if (mappingOperation.getOperationName () == null) {
throw new NullPointerException ("operation name is null");
}
if (mappingOperation.getInputAttributeType () == null) {
throw new NullPointerException ("attribute type is null");
}
return new InputOperationDTO (
findFeatureTypeAttribute (mappingOperation.getOperationName (), mappingOperation.getInputAttributeType ()),
mappingOperation.getOperationName (),
mappingOperation.getInputAttributeType ()
);
}
private FeatureTypeAttribute findFeatureTypeAttribute (final String name, final AttributeType type) {
for (final FeatureTypeAttribute attr: inputAttributes) {
if (attr.getName ().getLocalPart ().equals (name) && attr.getType ().equals (type)) {
return attr;
}
}
return null;
}
private TransformOperationDTO unmarshalTransformOperation (final MappingOperation mappingOperation) {
if (mappingOperation == null || mappingOperation.getOperationName () == null) {
throw new NullPointerException ();
}
// Unmarshal properties:
final OperationType operationType = findOperationType (mappingOperation.getOperationName ());
return new TransformOperationDTO (
operationType,
unmarshalInputs (mappingOperation.getInputs ()),
unmarshalProperties (mappingOperation.getProperties(), operationType)
);
}
private List<OperationInputDTO> unmarshalInputs (final List<MappingOperation> inputs) {
if (inputs == null) {
throw new NullPointerException ("inputs cannot be null");
}
final List<OperationInputDTO> result = new ArrayList<OperationInputDTO> ();
for (final MappingOperation input: inputs) {
result.add (new OperationInputDTO (unmarshalOperation (input)));
}
return result;
}
private Object unmarshalProperties (final String properties, final OperationType operationType) {
if (properties == null || operationType.getPropertyBeanClass () == null) {
return null;
}
try {
return new ObjectMapper ().readValue (properties, operationType.getPropertyBeanClass ());
} catch (JsonParseException e) {
throw new IllegalArgumentException (String.format ("Invalid JSON string %s", properties), e);
} catch (JsonMappingException e) {
throw new IllegalArgumentException (String.format ("Invalid JSON string %s", properties), e);
} catch (IOException e) {
throw new IllegalArgumentException (String.format ("Invalid JSON string %s", properties), e);
}
}
private OperationType findOperationType (final String name) {
for (final OperationType ot: operationTypes) {
if (ot.getName ().equals (name)) {
return ot;
}
}
throw new IllegalArgumentException (String.format ("No operation named %s could be found", name));
}
@Transactional
public void putAttributeMapping (final Dataset dataset, final OutputOperationType attribute, final OperationDTO operation, final boolean isValid) {
final AttributeMapping attributeMapping = getOrCreateAttributeMapping (dataset, attribute);
attributeMapping.setValid (isValid);
// Persist the root operation:
attributeMapping.setRootOperation (persistOperation (operation, attributeMapping.getRootOperation ()));
managerDao.update (attributeMapping);
}
private MappingOperation persistOperation (final OperationDTO operation, final MappingOperation existingMappingOperation) {
if (operation == null) {
// Delete an existing mapping operation:
if (existingMappingOperation != null) {
deleteChild (existingMappingOperation);
}
return null;
}
final MappingOperation mappingOperation = existingMappingOperation != null
? existingMappingOperation
: new MappingOperation ();
// Set properties of this mapping operation:
mergeOperation (operation, mappingOperation);
// Set children:
// Persist:
if (mappingOperation.getId () == null) {
managerDao.create (mappingOperation);
} else {
managerDao.update (mappingOperation);
}
return mappingOperation;
}
private void mergeOperation (final OperationDTO operation, final MappingOperation mappingOperation) {
if (operation instanceof InputOperationDTO) {
mergeInputOperation ((InputOperationDTO)operation, mappingOperation);
} else if (operation instanceof TransformOperationDTO) {
mergeTransformOperation ((TransformOperationDTO)operation, mappingOperation);
} else {
throw new IllegalArgumentException (String.format ("Invalid operation type %s", operation.getClass ().getCanonicalName ()));
}
}
private void mergeInputOperation (final InputOperationDTO inputOperation, final MappingOperation operation) {
// If the operation previously had children, delete them:
for (final MappingOperation input: operation.getInputs ()) {
deleteChild (input);
}
// Set properties:
operation.setOperationType (MappingOperationType.INPUT_OPERATION);
operation.setInputAttributeType (inputOperation.getAttributeType ());
operation.setOperationName (inputOperation.getAttributeName ());
operation.setInputs (new ArrayList<MappingOperation> ());
operation.setProperties (null);
}
private void mergeTransformOperation (final TransformOperationDTO transformOperation, final MappingOperation operation) {
// Set properties:
operation.setOperationType (MappingOperationType.TRANSFORM_OPERATION);
operation.setOperationName (transformOperation.getOperationType ().getName ());
operation.setInputAttributeType (null);
// Serialize configuration properties:
operation.setProperties (serializeProperties (transformOperation.getOperationProperties ()));
// Update children:
final List<OperationInput> operationInputs = transformOperation.getInputs ();
final List<MappingOperation> oldChildren = new ArrayList<MappingOperation> (operation.getInputs ());
final List<MappingOperation> newChildren = new ArrayList<MappingOperation> ();
int i;
for (i = 0; i < operationInputs.size (); ++ i) {
final OperationInputDTO operationInput = (OperationInputDTO)operationInputs.get (i);
final MappingOperation childOperation = i < oldChildren.size () ? oldChildren.get (i) : null;
newChildren.add (persistOperation ((OperationDTO)operationInput.getOperation (), childOperation));
}
// Remove unused children:
for (; i < oldChildren.size (); ++ i) {
deleteChild (oldChildren.get (i));
}
operation.setInputs (newChildren);
}
private void deleteChild (final MappingOperation mappingOperation) {
if (mappingOperation == null) {
return;
}
// Delete children of this operation:
final List<MappingOperation> children = mappingOperation.getInputs ();
if (children != null) {
for (final MappingOperation child: children) {
deleteChild (child);
}
}
// Delete this operation:
managerDao.delete (mappingOperation);
}
private String serializeProperties (final Object properties) {
if (properties == null) {
return null;
}
try {
return new ObjectMapper ().writeValueAsString (properties);
} catch (JsonGenerationException e) {
throw new RuntimeException (e);
} catch (JsonMappingException e) {
throw new RuntimeException (e);
} catch (IOException e) {
throw new RuntimeException (e);
}
}
/**
* Returns an existing mapping for the given attribute and dataset, or creates and persists a new one if
* it hasn't been created yet.
*
* @param dataset
* @param attribute
* @return An attribute mapping for the given dataset and attribute.
*/
private AttributeMapping getOrCreateAttributeMapping (final Dataset dataset, final OutputOperationType attribute) {
final AttributeMapping existingMapping = managerDao.getAttributeMapping (dataset, attribute.getName ());
if (existingMapping == null) {
final AttributeMapping mapping = new AttributeMapping (dataset, attribute.getName ());
managerDao.create (mapping);
return mapping;
}
return existingMapping;
}
}