/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.transformation.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.mapping.Mapping;
import org.eclipse.emf.mapping.MappingRoot;
import org.teiid.core.designer.ModelerCoreException;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.metadata.runtime.MetadataRecord;
import org.teiid.designer.metamodels.transformation.TransformationFactory;
import org.teiid.designer.query.IQueryService;
import org.teiid.designer.query.metadata.IMetadataID;
import org.teiid.designer.query.sql.IElementCollectorVisitor;
import org.teiid.designer.query.sql.lang.ICommand;
import org.teiid.designer.query.sql.lang.IExpression;
import org.teiid.designer.query.sql.lang.util.CommandHelper;
import org.teiid.designer.query.sql.symbol.IElementSymbol;
import org.teiid.designer.transformation.TransformationPlugin;
/**
* AttributeMappingHelper - static methods for keeping the target attribute mappings for
* a transformation up-to-date.
*
* @since 8.0
*/
public class AttributeMappingHelper {
private static final boolean NOT_SIGNIFICANT = false;
private static final boolean IS_UNDOABLE = true;
/**
* Set the attribute mappings using the current SELECT command and target attributes.
* @param transMappingRoot the TransformationMappingRoot
* @param txnSource the source for the transaction
* @return true if any data was changed.
*/
public static boolean updateAttributeMappings(Object transMappingRoot, Object txnSource) {
boolean changed = false;
if(!TransformationHelper.isTransformationMappingRoot(transMappingRoot)) {
return changed;
}
// Get Target attributes
List targetAttrs = TransformationHelper.getTransformationTargetAttributes((EObject)transMappingRoot);
if( targetAttrs == null || targetAttrs.isEmpty() ) {
return changed;
}
// --------------------- BML 3/21/07 --------------------------
// Defect 23839 - Added a new method check here to improve performance. If ALL attributes are fully mapped, the check is
// more efficient than assuming they may not be and performing the remainder of the method.
if( attributesFullyMapped(transMappingRoot, targetAttrs) ) {
// Backed out 2/27/07
// TODO: Fix in QueryReconcilerHelper.applyModifications() to actually look at each binding, set any changed references
// so this can be uncommented and sped up again.
// return changed;
}
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Update attr mappings",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Fix for case 2778 - clean out bad mappings (no outputs)
cleanAttributeMappings(transMappingRoot,txnSource);
// Ensure each attribute has a mapping
changed = reconcileAttributeMappings(transMappingRoot,targetAttrs,txnSource);
// Set mapping inputs using SQL
boolean isValid = SqlMappingRootCache.isSelectValid(transMappingRoot);
if(!isValid) {
return changed;
}
// Get the SQL Command and the Projected Symbols
ICommand command = SqlMappingRootCache.getSelectCommand(transMappingRoot);
List projectedSymbols = CommandHelper.getProjectedSymbols(command);
// Get the target attributes that arent specified in an accessPattern
List attrs = TransformationHelper.getTransformationTargetAttributes((EObject)transMappingRoot);
IQueryService queryService = ModelerCore.getTeiidQueryService();
// Iterate attributes, setting each mapping
Iterator iter = attrs.iterator();
while(iter.hasNext()) {
EObject attr = (EObject)iter.next();
if(TransformationHelper.isSqlColumn(attr)) {
// Attribute name
String colName = TransformationHelper.getSqlEObjectName(attr);
// If mapping doesnt exist, create one
if( !hasAttributeMapping(transMappingRoot,attr) ) {
createAttributeMapping(transMappingRoot,attr,txnSource);
changed = true;
}
// Find element matching the attribute name (if it exists)
IExpression seSymbol = getSymbolWithName(projectedSymbols,colName);
if(seSymbol!=null) {
/**
* Note as per TEIIDDES-1612, the elementCollectorVisitor implementations stores
* the found elements in a field so reuse of an existing instance must be avoided
* when using a loop.
*/
// Get the ElementSymbols / corresponding EObjs
IElementCollectorVisitor elementCollectorVisitor = queryService.getElementCollectorVisitor(true);
Collection elemSymbols = elementCollectorVisitor.findElements(seSymbol);
Collection<EObject> elemEObjs = TransformationSqlHelper.getElementSymbolEObjects(elemSymbols,command);
// Set Elem EObjs as inputs for attr Mapping
changed = setAttributeMapping(transMappingRoot,attr,elemEObjs,txnSource) || changed;
}
}
}
succeeded = true;
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
return changed;
}
/**
* Make sure there is an attribute mapping for each target Attribute in the supplied List.
* Remove any extras.
* @param transMappingRoot the Transformation MappingRoot
* @param targetAttrs the List of target attributes
* @param txnSource the source for the transaction
* @return true if any data was changed.
*/
private static boolean reconcileAttributeMappings(Object transMappingRoot,List targetAttrs,Object txnSource) {
boolean changed = false;
if(TransformationHelper.isTransformationMappingRoot(transMappingRoot) && targetAttrs!=null) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Reconcile attr mappings",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Get the current mapped attributes
List currentMappedAttributes = getMappedAttributes(transMappingRoot);
// Create Mappings for those missing
Iterator iter = targetAttrs.iterator();
while(iter.hasNext()) {
EObject targetAttr = (EObject)iter.next();
// If new target is not already mapped, add it
if(!currentMappedAttributes.contains(targetAttr)) {
// Create the Attribute Mapping - nested under mapping root
TransformationFactory mappingFactory = TransformationFactory.eINSTANCE;
Mapping attrMapping = mappingFactory.createTransformationMapping();
attrMapping.setNestedIn((MappingRoot)transMappingRoot);
// Set the output to the supplied target
List outputs = attrMapping.getOutputs();
outputs.add(targetAttr);
changed = true;
}
}
// Remove extraneous mappings
for(int i=currentMappedAttributes.size()-1; i>=0; i--) {
EObject mappingAttr = (EObject)currentMappedAttributes.get(i);
if(!targetAttrs.contains(mappingAttr)) {
removeAttributeMapping(transMappingRoot,mappingAttr,txnSource);
changed = true;
}
}
succeeded = true;
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
return changed;
}
/**
* Create a new attribute mapping nested under the SqlTransformationMappingRoot. If a mapping
* for the supplied attribute already exists, then a new one is not created.
* @param transMappingRoot the Transformation MappingRoot
* @param targetAttr the attribute for the new mapping
* @param txnSource the source for the transaction
*/
public static void createAttributeMapping(Object transMappingRoot,EObject targetAttr,Object txnSource) {
if(TransformationHelper.isTransformationMappingRoot(transMappingRoot)
&& targetAttr!=null && TransformationHelper.isSqlColumn(targetAttr) ) {
// Get the current mapped attributes
List currentMappedAttributes = getMappedAttributes(transMappingRoot);
// If new target is not already mapped, add it
if(!currentMappedAttributes.contains(targetAttr)) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Create attr mapping",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Create the Attribute Mapping - nested under mapping root
TransformationFactory mappingFactory = TransformationFactory.eINSTANCE;
Mapping attrMapping = mappingFactory.createTransformationMapping();
attrMapping.setNestedIn((MappingRoot)transMappingRoot);
// Set the output to the supplied target
List outputs = attrMapping.getOutputs();
outputs.add(targetAttr);
succeeded = true;
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
}
}
/**
* Get the attribute mapping for the supplied target attribute
* @param transMappingRoot the Transformation MappingRoot
* @param targetAttr the mapping output EObject
*/
public static Mapping getAttributeMapping(Object transMappingRoot,EObject targetAttr) {
// result
Mapping attrMapping = null;
// Get all mappings
List attrMappings = getAttributeMappings(transMappingRoot);
// Iterate mappings, look for output matching the supplied targetAttr
Iterator iter = attrMappings.iterator();
while(iter.hasNext()) {
EObject eObj = (EObject)iter.next();
if(eObj instanceof Mapping) {
List targets = ((Mapping)eObj).getOutputs();
if(!targets.isEmpty()) {
EObject target = (EObject)targets.get(0);
if(target!=null && target.equals(targetAttr)) {
attrMapping = (Mapping)eObj;
break;
}
}
}
}
return attrMapping;
}
/**
* Determine if there is an attribute mapping for the supplied target attribute
* @param transMappingRoot the Transformation MappingRoot
* @param targetAttr the mapping output EObject
* @return 'true' if there is an attribute mapping, 'false' if not.
*/
public static boolean hasAttributeMapping(Object transMappingRoot,EObject targetAttr) {
// result
boolean hasMapping = false;
// Get all mappings
List attrMappings = getAttributeMappings(transMappingRoot);
// Iterate mappings, look for output matching the supplied targetAttr
Iterator iter = attrMappings.iterator();
while(iter.hasNext()) {
EObject eObj = (EObject)iter.next();
if(eObj instanceof Mapping) {
List targets = ((Mapping)eObj).getOutputs();
if(!targets.isEmpty()) {
EObject target = (EObject)targets.get(0);
if(target!=null && target.equals(targetAttr)) {
hasMapping = true;
break;
}
}
}
}
return hasMapping;
}
/**
* Remove an attribute mapping nested under the SqlTransformationMappingRoot.
* @param transMappingRoot the Transformation MappingRoot
* @param targetAttr the mapping output EObject
* @param txnSource the source for the transaction
*/
public static void removeAttributeMapping(Object transMappingRoot,EObject targetAttr,Object txnSource) {
Mapping mapping = getAttributeMapping(transMappingRoot,targetAttr);
if(mapping!=null) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Remove attr mapping",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
ModelerCore.getModelEditor().delete(mapping);
succeeded = true;
} catch (ModelerCoreException e) {
String message = TransformationPlugin.Util.getString("AttributeMappingHelper.removeAttrMappingError", //$NON-NLS-1$
transMappingRoot.toString());
TransformationPlugin.Util.log(IStatus.ERROR, e, message);
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
}
/**
* Add an attribute mapping nested under the SqlTransformationMappingRoot.
* @param transMappingRoot the Transformation MappingRoot
* @param targetAttr the mapping output EObject
* @param sourceAttrs the List of input EObjects
* @param txnSource the source for the transaction
* @return true if any data was changed.
*/
public static boolean setAttributeMapping(Object transMappingRoot,
EObject targetAttr, Collection sourceAttrs,
Object txnSource ) {
boolean changed = false;
// Get Mapping for the attribute
Mapping attrMapping = getAttributeMapping(transMappingRoot,targetAttr);
if(attrMapping!=null) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Set attr mapping",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Set the inputs to the mapping
List inputs = attrMapping.getInputs();
// Dont use clear - notification issue
Iterator iter = inputs.iterator();
while(iter.hasNext()) {
// defect 16726 - unneeded events were being fired
Object currInput = iter.next();
// see if we really should remove the attribute by checking
// if we will be adding the same back in:
if (sourceAttrs.contains(currInput)) {
// it is in the list to be added; leave it in inputs,
// and remove it from the list so it won't be added
sourceAttrs.remove(currInput);
} else {
// go ahead and remove, since it is not to be added:
iter.remove();
changed = true;
} // endif
}
// Add sources
iter = sourceAttrs.iterator();
while(iter.hasNext()) {
Object attr = iter.next();
if(attr!=null) {
inputs.add(attr);
changed = true;
}
}
succeeded = true;
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
return changed;
}
/**
* Clear all of the current attribute mapping inputs for the supplied mappingRoot transformation.
* @param transMappingRoot the Transformation MappingRoot
* @param txnSource the source for the transaction
*/
public static void clearAttributeMappingInputs(Object transMappingRoot,Object txnSource) {
List attrMappings = getAttributeMappings(transMappingRoot);
if(!attrMappings.isEmpty()) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Clear attr mappings",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Go thru the Mappings and remove the "inputs"
Iterator iter = attrMappings.iterator();
while(iter.hasNext()) {
Mapping mapping = (Mapping)iter.next();
List mappingInputs = mapping.getInputs();
// Dont use clear - notification issue
Iterator mIter = mappingInputs.iterator();
while(mIter.hasNext()) {
mIter.next(); mIter.remove();
}
}
succeeded = true;
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
}
/**
* Delete any mappings without an output. (Case 2778 fix)
* @param transMappingRoot
* @param txnSource
* @since 5.0
*/
public static void cleanAttributeMappings(Object transMappingRoot,Object txnSource) {
List attrMappings = new ArrayList(getAttributeMappings(transMappingRoot));
// For efficency sake, let's look for "STALE" mappings first, then we don't create a transaction of no work is necessary.
// Find the stale mappings so we only delete if there are any
Iterator iter = attrMappings.iterator();
Collection staleMappings = new ArrayList();
while(iter.hasNext()) {
Mapping mapping = (Mapping)iter.next();
List outputs = mapping.getOutputs();
if(outputs==null || outputs.size()==0) {
staleMappings.add(mapping);
}
}
if(!staleMappings.isEmpty()) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"check attr mappings",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Remove the mappings with no outputs
ModelerCore.getModelEditor().delete(staleMappings);
succeeded = true;
} catch (ModelerCoreException e) {
String message = TransformationPlugin.Util.getString("AttributeMappingHelper.cleanAttrMappingError", //$NON-NLS-1$
transMappingRoot.toString());
TransformationPlugin.Util.log(IStatus.ERROR, e, message);
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
}
/**
* Clear all of the current attribute mapping inputs for the supplied mappingRoot transformation.
* @param transMappingRoot the Transformation MappingRoot
* @param txnSource the source for the transaction
*/
public static void clearAttributeMappings(Object transMappingRoot,Object txnSource) {
List attrMappings = new ArrayList(getAttributeMappings(transMappingRoot));
if(!attrMappings.isEmpty()) {
// start txn if not already in txn
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT,IS_UNDOABLE,"Clear attr mappings",txnSource); //$NON-NLS-1$
boolean succeeded = false;
try {
// Using the alternative delete(Collection) call instead of interating.
ModelerCore.getModelEditor().delete(attrMappings);
succeeded = true;
} catch (ModelerCoreException e) {
String message = TransformationPlugin.Util.getString("AttributeMappingHelper.removeAttrMappingError", //$NON-NLS-1$
transMappingRoot.toString());
TransformationPlugin.Util.log(IStatus.ERROR, e, message);
} finally {
// If we start txn, commit it
if(requiredStart) {
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
}
/**
* Get a list of Target Attributes that have an attribute mapping.
* @param transMappingRoot the Transformation MappingRoot
* @return the List of attributes that have a mapping
*/
private static List getMappedAttributes(Object transMappingRoot) {
List attrMappings = getAttributeMappings(transMappingRoot);
List mappedAttrs = new ArrayList(attrMappings.size());
// Get the attribute from each mapping
Iterator iter = attrMappings.iterator();
while(iter.hasNext()) {
EObject eObj = (EObject)iter.next();
if(eObj instanceof Mapping) {
// should only be one output - the attribute
List targets = ((Mapping)eObj).getOutputs();
if(!targets.isEmpty()) {
mappedAttrs.add(targets.get(0));
}
}
}
return mappedAttrs;
}
/**
* Determine if a t-root's target attributes are fully mapped
* @param transMappingRoot
* @return
* @since 5.0
*/
private static boolean attributesFullyMapped(Object transMappingRoot, List targetAttributes) {
// --------------------- BML 3/21/07 --------------------------
// Defect 23839 - Added a new method check to improve performance. If ALL attributes are fully mapped, the check is
// more efficient than assuming they may not be and performing the remainder of the method.
//
// A FULLY Mapped attribute has a mapping where the Virtual Column is the OUTPUT and there is one or more INPUT's.
List attrMappings = getAttributeMappings(transMappingRoot);
if( attrMappings.size() != targetAttributes.size() ) {
return false;
}
List localAttrList = new ArrayList(targetAttributes);
Iterator iter = attrMappings.iterator();
while(iter.hasNext()) {
EObject eObj = (EObject)iter.next();
if(eObj instanceof Mapping) {
Mapping mapping = (Mapping)eObj;
// should only be one output - the attribute
if(mapping.getOutputs()==null || mapping.getOutputs().isEmpty() ) {
return false;
}
// Should be at least one Input
if( mapping.getInputs() == null || mapping.getInputs().isEmpty() ) {
return false;
}
localAttrList.remove(mapping.getOutputs().get(0));
}
}
return localAttrList.isEmpty();
}
/**
* Get all of the attribute mappings for the supplied SqlTransformationMappingRoot.
* @param transMappingRoot the Transformation MappingRoot
* @return the List of attribute Mappings
*/
public static List getAttributeMappings(Object transMappingRoot) {
List result = Collections.EMPTY_LIST;
if(transMappingRoot!=null && TransformationHelper.isTransformationMappingRoot(transMappingRoot)) {
// Get the Attribute Mapping List and clear it
result = ((MappingRoot)transMappingRoot).getNested();
}
return result;
}
/**
* Find the SingleElementSymbol in the supplied List of SingleElementSymbols
* with the supplied name.
* @param seSymbols the List of SingleElementSymbols
* @param name the symbol name
* @return the SingleElementSymbol from the List with the provided name, null if not found.
*/
private static IExpression getSymbolWithName(List seSymbols, String name) {
IExpression result = null;
Iterator iter = seSymbols.iterator();
while(iter.hasNext()) {
IExpression seSymbol = (IExpression)iter.next();
String symbolName = TransformationSqlHelper.getSingleElementSymbolShortName(seSymbol,false);
if(symbolName!=null && symbolName.equalsIgnoreCase(name)) {
result = seSymbol;
break;
}
}
return result;
}
/**
* The symbol might be a UUID, lookup the MetadataRecord, for the
* actual name.
* @param symbol
*/
public static String getSymbolShortName(final IExpression symbol) {
String fullName = getSymbolFullName(symbol);
int index = fullName.lastIndexOf("."); //$NON-NLS-1$
return fullName.substring(index+1);
}
/**
* The symbol might be a UUID, lookup the MetadataRecord, for the
* actual name.
*/
public static String getSymbolFullName(final IExpression symbol) {
CoreArgCheck.isNotNull(symbol);
if(symbol instanceof IElementSymbol) {
Object metadataID = ((IElementSymbol)symbol).getMetadataID();
if(metadataID instanceof MetadataRecord) {
MetadataRecord record = (MetadataRecord) metadataID;
return record.getFullName();
}
else if (metadataID instanceof IMetadataID) {
IMetadataID tempID = (IMetadataID) metadataID;
return tempID.getID();
}
}
IQueryService queryService = ModelerCore.getTeiidQueryService();
return queryService.getSymbolName(symbol);
}
}