/*
* 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.ui.reconciler;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
import org.teiid.designer.core.metamodel.aspect.sql.SqlColumnAspect;
import org.teiid.designer.core.query.QueryValidationResult;
import org.teiid.designer.core.query.QueryValidator;
import org.teiid.designer.core.query.SetQueryUtil;
import org.teiid.designer.metamodels.transformation.MappingClass;
import org.teiid.designer.metamodels.transformation.SqlTransformationMappingRoot;
import org.teiid.designer.query.sql.lang.ICommand;
import org.teiid.designer.query.sql.lang.IQueryCommand;
import org.teiid.designer.query.sql.lang.ISetQuery;
import org.teiid.designer.transformation.ui.UiConstants;
import org.teiid.designer.transformation.ui.UiPlugin;
import org.teiid.designer.transformation.util.AttributeMappingHelper;
import org.teiid.designer.transformation.util.SqlMappingRootCache;
import org.teiid.designer.transformation.util.TransformationHelper;
import org.teiid.designer.transformation.util.TransformationMappingHelper;
import org.teiid.designer.transformation.validation.TransformationValidator;
/**
* Reconciler Helper that the Reconciler Panel works with
*
*
* @since 8.0
*/
public class QueryReconcilerHelper {
private static final String REORDER_DETECTED_TITLE = UiConstants.Util.getString("QueryReconcilerHelper.reorderDetectedTitle"); //$NON-NLS-1$
private static final String REORDER_DETECTED_MESSAGE = UiConstants.Util.getString("QueryReconcilerHelper.reorderDetectedMessage"); //$NON-NLS-1$
//============================================================
// Instance variables
//============================================================
private SqlTransformationMappingRoot transMappingRoot; // the transformation Object being worked on
private ReconcilerObject reconcilerObject = null;
private boolean isPrimarySelectClause = true;
//============================================================
// Constructors
//============================================================
/**
* Constructor.
*
* @param transformationObj the TransformationMappingRoot this is based on.
* @param unionQuerySegment the index of the union Query segment to reconcile, -1 if not
* a union query or no segment specified.
*/
public QueryReconcilerHelper(SqlTransformationMappingRoot transformationObj,
int unionQuerySegment) {
this.transMappingRoot = transformationObj;
init(this.transMappingRoot,unionQuerySegment);
}
//============================================================
// Instance methods
//============================================================
/**
* Initialize the reconciler helper.
* @param mappingRoot the TransformationMappingRoot object
* @param unionQuerySegment for a union query, this is the segment currenly being worked on.
* If not a union query, or not working in a segment, -1 is expected.
*/
private void init(SqlTransformationMappingRoot mappingRoot,int unionQuerySegment) {
if(mappingRoot!=null) {
// Get the transformation SELECT command
ICommand originalCommand = SqlMappingRootCache.getSelectCommand(mappingRoot);
if(originalCommand instanceof ISetQuery && unionQuerySegment!=-1) {
List queries = SetQueryUtil.getQueryList((ISetQuery)originalCommand);
originalCommand = (ICommand)queries.get(unionQuerySegment);
// If command is not resolved, attempt to resolve it.
if(!originalCommand.isResolved()) {
QueryValidator validator = new TransformationValidator(mappingRoot);
QueryValidationResult result = validator.validateSql(originalCommand.toString(), QueryValidator.SELECT_TRNS, false);
if(result.isResolvable()) {
originalCommand = result.getCommand();
}
}
}
// Get the target Attributes
EObject virtualTarget = TransformationHelper.getTransformationTarget(mappingRoot);
// Check if output is locked
boolean isLocked = mappingRoot.isOutputReadOnly();
this.reconcilerObject = new ReconcilerObject(virtualTarget,originalCommand,isLocked);
if( unionQuerySegment > 0)
this.isPrimarySelectClause = false;
}
}
/**
* Apply all of the pending modifications
* @param uIndex the union segment index to apply changes to. For non-union, -1 is supplied
* @param txnSource the transaction source
*/
public void applyAllModifications(int uIndex, Object txnSource) {
if(this.reconcilerObject.hasValidModifications()) {
// If reconciling a union segment (other than the first), only the sql is updated.
if(uIndex>0) {
applyUnionSegmentModifications(uIndex,txnSource);
return;
}
// Target Attributes Deleted
if(this.reconcilerObject.hasTargetAttributesToDelete()) {
List attributes = this.reconcilerObject.getTargetAttributesToDelete();
TransformationMappingHelper.removeTargetAttributes(this.transMappingRoot,attributes,true,txnSource);
}
// If there are target attrbutes to create, create them (only if unlocked AND createAttributesOnExit == TRUE)
if( TransformationMappingHelper.shouldCreateTargetAttributes() &&
!this.reconcilerObject.isTargetLocked() &&
this.reconcilerObject.hasTargetAttributesToCreate()) {
List attributeNames = this.reconcilerObject.getTargetAttributeNamesToCreate();
EObject targetGroup = TransformationHelper.getTransformationTarget(this.transMappingRoot);
TransformationMappingHelper.addTargetAttributes(targetGroup,attributeNames,txnSource);
// Transfer Lengths for newly created attributes
// Set lengths if not mappingClass attributes
if( !(targetGroup instanceof MappingClass) ) {
// Get the Map of Symbol Name to the lengths for Strings, (-1) if not Datatype
Map attrLengthMap = this.reconcilerObject.getCreatedAttrLengthMap();
// Set attribute lengths for String datatypes
TransformationMappingHelper.setGroupAttributeLengths(targetGroup,attrLengthMap);
}
// find attribute EObject for each new attribute and set binding
// the binding object is used later to determine the attribute type
List allAttributes = TransformationHelper.getTransformationTargetAttributes(transMappingRoot);
for (int numAttributes = allAttributes.size(), i = 0; i < numAttributes; i++) {
EObject attr = (EObject)allAttributes.get(i);
if (org.teiid.designer.core.metamodel.aspect.sql.SqlAspectHelper.isColumn(attr)) {
SqlColumnAspect columnAspect = (SqlColumnAspect)org.teiid.designer.core.metamodel.aspect.sql.SqlAspectHelper.getSqlAspect(attr);
String name = columnAspect.getName(attr);
if (hasNameMatch(attributeNames,name)) {
// set binding here
List bindings = this.reconcilerObject.getBindingList().getAll();
BINDINGS_LOOP:
for (int numBindings = bindings.size(), j = 0; j < numBindings; j++) {
Binding binding = (Binding)bindings.get(j);
if (binding.getAttributeName().equalsIgnoreCase(name)) {
binding.setAttribute(attr);
break BINDINGS_LOOP;
}
}
}
}
}
}
// Target Attribute Type Changes
if(this.reconcilerObject.hasTargetAttributeTypeMods()) {
this.reconcilerObject.applyTargetAttributeTypeMods(txnSource);
}
// SQL Modifications
if(uIndex==0) {
applyUnionSegmentModifications(uIndex,txnSource);
} else if(this.reconcilerObject.hasValidSqlModifications()) {
String newSql = this.reconcilerObject.getModifiedSql();
TransformationHelper.setSelectSqlString(this.transMappingRoot,newSql,true,txnSource);
}
AttributeMappingHelper.updateAttributeMappings(this.transMappingRoot,txnSource);
// Check ordering
TransformationMappingHelper.orderGroupAttributes(this.transMappingRoot, false, null);
}
}
public void applyPreModifications(Object txnSource) {
if(this.reconcilerObject.hasTargetAttributesToRename()) {
this.reconcilerObject.applyTargetAttributeRenames(txnSource);
}
}
public boolean hasPreModifications() {
return this.reconcilerObject.hasTargetAttributesToRename();
}
/**
* Get this helpers Reconciler business object.
* @return the ReconcilerObject for this helper
* @since 4.2
*/
public ReconcilerObject getReconcilerObject() {
return this.reconcilerObject;
}
/**
* Set the Target Virtual Group Locked state
* @param shouldLock 'true' if the transformation target group is to be Locked (Readonly), 'false' if not.
*/
public void setTargetLocked(boolean shouldLock) {
// If different than current state, change it.
if(transMappingRoot!=null && transMappingRoot.isOutputReadOnly()!=shouldLock) {
transMappingRoot.setOutputReadOnly(shouldLock);
}
this.reconcilerObject.setTargetLocked(shouldLock);
}
/**
* Method to apply changes in the case of a union (SetQuery).
* @param uIndex the index of the query segment being worked on (-1 if none)
* @param txnSource the transaction source
* @since 4.2
*/
private void applyUnionSegmentModifications(int uIndex,Object txnSource) {
// Create Target Attributes Created (only if unlocked)
if(!this.reconcilerObject.isTargetLocked() && this.reconcilerObject.hasTargetAttributesToCreate()) {
List attributeNames = this.reconcilerObject.getTargetAttributeNamesToCreate();
EObject targetGroup = TransformationHelper.getTransformationTarget(this.transMappingRoot);
TransformationMappingHelper.addTargetAttributes(targetGroup,attributeNames,txnSource);
// Transfer Lengths for newly created attributes
// Set lengths if not mappingClass attributes
if( !(targetGroup instanceof MappingClass) ) {
// Get the Map of Symbol Name to the lengths for Strings, (-1) if not Datatype
Map attrLengthMap = this.reconcilerObject.getCreatedAttrLengthMap();
// Set attribute lengths for String datatypes
TransformationMappingHelper.setGroupAttributeLengths(targetGroup,attrLengthMap);
}
// find attribute EObject for each new attribute and set binding
// the binding object is used later to determine the attribute type
List allAttributes = TransformationHelper.getTransformationTargetAttributes(transMappingRoot);
for (int numAttributes = allAttributes.size(), i = 0; i < numAttributes; i++) {
EObject attr = (EObject)allAttributes.get(i);
if (org.teiid.designer.core.metamodel.aspect.sql.SqlAspectHelper.isColumn(attr)) {
SqlColumnAspect columnAspect = (SqlColumnAspect)org.teiid.designer.core.metamodel.aspect.sql.SqlAspectHelper.getSqlAspect(attr);
String name = columnAspect.getName(attr);
if (hasNameMatch(attributeNames,name)) {
// set binding here
List bindings = this.reconcilerObject.getBindingList().getAll();
BINDINGS_LOOP:
for (int numBindings = bindings.size(), j = 0; j < numBindings; j++) {
Binding binding = (Binding)bindings.get(j);
if (binding.getAttributeName().equalsIgnoreCase(name)) {
binding.setAttribute(attr);
break BINDINGS_LOOP;
}
}
}
}
}
}
// SQL Modifications
if(this.reconcilerObject.hasValidSqlModifications()) {
String newSegmentSql = this.reconcilerObject.getModifiedSql();
QueryValidator validator = new TransformationValidator(this.transMappingRoot);
QueryValidationResult result = validator.validateSql(newSegmentSql, QueryValidator.SELECT_TRNS, false);
IQueryCommand newSegmentCommand = null;
if(result.isParsable()) {
newSegmentCommand = (IQueryCommand)result.getCommand();
}
ICommand command = SqlMappingRootCache.getSelectCommand(this.transMappingRoot);
if(command!=null && command instanceof ISetQuery) {
ISetQuery unionQuery = (ISetQuery)command.clone();
SetQueryUtil.setQueryAtIndex(unionQuery, uIndex, newSegmentCommand);
String newUnionSql = unionQuery.toString();
TransformationHelper.setSelectSqlString(this.transMappingRoot,newUnionSql,true,txnSource);
}
}
if(this.reconcilerObject.hasTargetAttributesToDelete()) {
List attributes = this.reconcilerObject.getTargetAttributesToDelete();
TransformationMappingHelper.removeTargetAttributes(this.transMappingRoot,attributes,true,txnSource);
}
// Target Attribute Name Changes
if(this.reconcilerObject.hasTargetAttributesToRename()) {
this.reconcilerObject.applyTargetAttributeRenames(txnSource);
}
// Target Attribute Type Changes
if(this.reconcilerObject.hasTargetAttributeTypeMods()) {
this.reconcilerObject.applyTargetAttributeTypeMods(txnSource);
}
AttributeMappingHelper.updateAttributeMappings(this.transMappingRoot,txnSource);
// Check ordering
boolean wasReordered = TransformationMappingHelper.orderGroupAttributes(this.transMappingRoot, true, reconcilerObject.getModifiedCommand());
// Throw up a dialog here that warns the user that they need to reconcile
// ALL UNION SEGMENTS to insure ordering of all Symbols is as desired.
if( wasReordered ) {
Shell shell = UiPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
MessageDialog.openWarning( shell, REORDER_DETECTED_TITLE, REORDER_DETECTED_MESSAGE);
}
}
/**
* Determine if the list has an entry matching the supplied String (ignoring the case).
* @param list the list of Strings
* @param str the str to look for in the list
* @return 'true' if name match exists in the list, 'false' if not.
*/
private boolean hasNameMatch(List list, String str) {
boolean hasMatch = false;
Iterator listIter = list.iterator();
// Iterate list1, look for match in list2
while(listIter.hasNext()) {
String listStr = (String)listIter.next();
if(listStr!=null && listStr.equalsIgnoreCase(str)) {
hasMatch = true;
break;
}
}
return hasMatch;
}
public boolean isPrimarySelectClause() {
return this.isPrimarySelectClause;
}
}