/*
* 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.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.builder.ModelBuildUtil;
import org.teiid.designer.core.notification.util.NotificationUtilities;
import org.teiid.designer.core.query.QueryValidator;
import org.teiid.designer.core.resource.EmfResource;
import org.teiid.designer.core.util.ModelObjectCollector;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.core.workspace.ModelWorkspaceException;
import org.teiid.designer.metamodels.core.ModelType;
import org.teiid.designer.metamodels.function.FunctionParameter;
import org.teiid.designer.metamodels.function.ReturnParameter;
import org.teiid.designer.metamodels.function.ScalarFunction;
import org.teiid.designer.metamodels.relational.Procedure;
import org.teiid.designer.metamodels.relational.ProcedureParameter;
import org.teiid.designer.metamodels.relational.RelationalPackage;
import org.teiid.designer.metamodels.transformation.SqlAlias;
import org.teiid.designer.metamodels.transformation.SqlTransformation;
import org.teiid.designer.metamodels.transformation.SqlTransformationMappingRoot;
import org.teiid.designer.transformation.ui.UiConstants;
import org.teiid.designer.transformation.ui.editors.TransformationObjectEditorPage;
import org.teiid.designer.transformation.util.SqlMappingRootCache;
import org.teiid.designer.transformation.util.TransformationHelper;
import org.teiid.designer.ui.editors.ModelEditor;
import org.teiid.designer.ui.editors.ModelEditorManager;
import org.teiid.designer.ui.editors.ModelObjectEditorPage;
import org.teiid.designer.ui.event.ModelResourceEvent;
import org.teiid.designer.ui.viewsupport.ModelIdentifier;
import org.teiid.designer.ui.viewsupport.ModelUtilities;
/**
* Implementation of {@link IModelNotificationHandler} to handle notifications for changes in function model objects.
*
* In particular, if a function method (name) changes, we'd like to invalidate the status for any transformation SQL
* that uses that method name, then perform the SQL rename operation.
*
* Also, if a function model is reloaded/changed or removed we'd like to invalidate any Transformation status objects
* for models dependent on that function model.
*
*
* @since 8.0
*/
public class FunctionModelNotificationHandler implements IModelNotificationHandler, UiConstants {
private boolean isFunctionModelObject( Object obj) {
if( obj instanceof ScalarFunction ||
obj instanceof FunctionParameter ||
obj instanceof ReturnParameter ) {
return true;
} else if( obj instanceof Procedure ) {
return ((Procedure)obj).isFunction();
} else if(obj instanceof ProcedureParameter) {
return ((ProcedureParameter)obj).getProcedure().isFunction();
}
return false;
}
/*
* Get all Function Model object change Notifications.
* @param notifications the collection of all notifications
* @return the Model Rename Notifications
*/
private Collection getModelChangeNotifications( Collection notifications,
Object source ) {
Collection result = null;
Iterator iter = notifications.iterator();
while (iter.hasNext()) {
Notification notification = (Notification)iter.next();
if (NotificationUtilities.isRemoved(notification) ||
NotificationUtilities.isAdded(notification) ||
NotificationUtilities.isChanged(notification) ) {
// Get the object that was changed
Object changedObj = ModelerCore.getModelEditor().getChangedObject(notification);
// changed object -
if (isFunctionModelObject(changedObj) ) {
if (result == null) {
result = new ArrayList(notifications.size());
}
result.add(notification);
// Remove from notifications collection
iter.remove();
} else if( changedObj != null && changedObj instanceof EmfResource) {
ModelResource mr = ModelUtilities.getModelResource((Resource)changedObj, false);
if( ModelIdentifier.isFunctionModel(mr) ) {
if (result == null) {
result = new ArrayList(notifications.size());
}
result.add(notification);
// Remove from notifications collection
iter.remove();
}
}
}
}
if (result == null) {
result = Collections.EMPTY_LIST;
}
return result;
}
private void handleModelChangeNotification(Collection notifications, Object txnSource) {
Set<ModelResource> changedMRs = new HashSet<ModelResource>();
Iterator iter = notifications.iterator();
while (iter.hasNext()) {
Notification notification = (Notification)iter.next();
if( NotificationUtilities.isAdded(notification) || NotificationUtilities.isRemoved(notification)) {
// Get all Transformation Roots for the resource and clean out the cache for ALL of them
Object changedObj = ModelerCore.getModelEditor().getChangedObject(notification);
ModelResource changedModelResource = null;
if( changedObj instanceof ScalarFunction ) {
EObject eObject = (EObject)changedObj;
changedModelResource = ModelUtilities.getModelResource(eObject);
// Get the dependent resources
} else if( changedObj instanceof EmfResource ) {
ModelResource mr = ModelUtilities.getModelResource((Resource)changedObj, false);
if( ModelIdentifier.isFunctionModel(mr) || isSourceModelWithPushdownFunction(mr) ) {
changedModelResource = mr;
}
}
if( changedModelResource != null ) {
Set<ModelResource> affectedDependentModelResources = new HashSet<ModelResource>();
affectedDependentModelResources.addAll(ModelUtilities.getResourcesThatUse(changedModelResource));
try {
// Get all transformations for each dependent resource and do a string replace on the SQL for
// the changed function
for( ModelResource modelResource : affectedDependentModelResources ) {
final EmfResource emfRes = (EmfResource)modelResource.getEmfResource();
final List transformations = emfRes.getModelContents().getTransformations();
for (Iterator i = transformations.iterator(); i.hasNext();) {
EObject eObj = (EObject)i.next();
if (eObj instanceof SqlTransformationMappingRoot) {
SqlTransformationMappingRoot mappingRoot = (SqlTransformationMappingRoot)eObj;
//System.out.println(" >> FunctionModelNotificationHandler.handleModelChanged() Invalidating Status for Object " + mappingRoot.getTarget());
SqlMappingRootCache.invalidateStatus(mappingRoot, true, txnSource);
}
}
rebuildImportsInTransaction(modelResource.getEmfResource());
}
} catch (ModelWorkspaceException ex) {
Util.log(IStatus.ERROR, ex, ex.getMessage());
}
}
} else if (NotificationUtilities.isChanged(notification)) {
// Get all Transformation Roots for the resource and clean out the cache for ALL of them
Object changedObj = ModelerCore.getModelEditor().getChangedObject(notification);
boolean dependentModelChanged = false;
ModelResource changedModelResource = null;
try {
if( changedObj instanceof EObject && isFunction((EObject)changedObj)) {
EObject eObject = (EObject)changedObj;
final EStructuralFeature nameFeature = ModelerCore.getModelEditor().getNameFeature(eObject);
// check that the change was to the "name" property
if( notification.getFeature().equals(nameFeature) ) {
// Get old name and new name
String oldName = notification.getOldStringValue();
String newName = notification.getNewStringValue();
// Get the model resource
changedModelResource = ModelUtilities.getModelResource(changedObj);
// Get the dependent resources
Set<ModelResource> affectedDependentModelResources = new HashSet<ModelResource>();
affectedDependentModelResources.addAll(ModelUtilities.getResourcesThatUse(changedModelResource));
// Get all transformations for each dependent resource and do a string replace on the SQL for
// the changed function
for( ModelResource modelResource : affectedDependentModelResources ) {
final EmfResource emfRes = (EmfResource)modelResource.getEmfResource();
ModelEditor mEditor = ModelEditorManager.getModelEditorForFile((IFile)modelResource.getCorrespondingResource(), false);
final List transformations = emfRes.getModelContents().getTransformations();
for (Iterator i = transformations.iterator(); i.hasNext();) {
EObject eObj = (EObject)i.next();
if (eObj instanceof SqlTransformationMappingRoot) {
SqlTransformationMappingRoot mappingRoot = (SqlTransformationMappingRoot)eObj;
String sql = TransformationHelper.getSelectSqlString(mappingRoot);
boolean statusWasInvalidated = false;
if( sql != null && (sql.contains(oldName) || sql.contains(oldName.toUpperCase()) ) ) {
String changedSql = null;
if( sql.contains(oldName) ) {
changedSql = sql.replaceAll(oldName, newName);
} else {
changedSql = sql.replaceAll(oldName.toUpperCase(), newName.toUpperCase());
}
statusWasInvalidated = true;
SqlMappingRootCache.invalidateStatus(mappingRoot, true, txnSource);
boolean handled = false;
if (mEditor != null) {
// Yes, check to see if dirty:
ModelObjectEditorPage moep = mEditor.getActiveObjectEditor();
if (moep != null && moep instanceof TransformationObjectEditorPage) {
TransformationObjectEditorPage transOEP = (TransformationObjectEditorPage)moep;
handled = updateSqlEditorPanel(transOEP, QueryValidator.SELECT_TRNS, mappingRoot, changedSql);
}
}
if( !handled ) {
dependentModelChanged = true;
updateSqlInTransaction(mappingRoot, QueryValidator.SELECT_TRNS, changedSql, NOT_SIGNIFICANT, txnSource);
}
}
sql = TransformationHelper.getInsertSqlString(mappingRoot);
if( sql != null && (sql.contains(oldName) || sql.contains(oldName.toUpperCase()) ) ) {
String changedSql = null;
if( sql.contains(oldName) ) {
changedSql = sql.replaceAll(oldName, newName);
} else {
changedSql = sql.replaceAll(oldName.toUpperCase(), newName.toUpperCase());
}
if( !statusWasInvalidated ) {
SqlMappingRootCache.invalidateStatus(mappingRoot, true, txnSource);
}
boolean handled = false;
if (mEditor != null) {
// Yes, check to see if dirty:
ModelObjectEditorPage moep = mEditor.getActiveObjectEditor();
if (moep != null && moep instanceof TransformationObjectEditorPage) {
TransformationObjectEditorPage transOEP = (TransformationObjectEditorPage)moep;
handled = updateSqlEditorPanel(transOEP, QueryValidator.INSERT_TRNS, mappingRoot, changedSql);
}
}
if( !handled ) {
dependentModelChanged = true;
updateSqlInTransaction(mappingRoot, QueryValidator.INSERT_TRNS, changedSql, NOT_SIGNIFICANT, txnSource);
}
}
sql = TransformationHelper.getUpdateSqlString(mappingRoot);
if( sql != null && (sql.contains(oldName) || sql.contains(oldName.toUpperCase()) ) ) {
String changedSql = null;
if( sql.contains(oldName) ) {
changedSql = sql.replaceAll(oldName, newName);
} else {
changedSql = sql.replaceAll(oldName.toUpperCase(), newName.toUpperCase());
}
if( !statusWasInvalidated ) {
SqlMappingRootCache.invalidateStatus(mappingRoot, true, txnSource);
}
boolean handled = false;
if (mEditor != null) {
// Yes, check to see if dirty:
ModelObjectEditorPage moep = mEditor.getActiveObjectEditor();
if (moep != null && moep instanceof TransformationObjectEditorPage) {
TransformationObjectEditorPage transOEP = (TransformationObjectEditorPage)moep;
handled = updateSqlEditorPanel(transOEP, QueryValidator.UPDATE_TRNS, mappingRoot, changedSql);
}
}
if( !handled ) {
dependentModelChanged = true;
updateSqlInTransaction(mappingRoot, QueryValidator.UPDATE_TRNS, changedSql, NOT_SIGNIFICANT, txnSource);
}
}
sql = TransformationHelper.getDeleteSqlString(mappingRoot);
if( sql != null && (sql.contains(oldName) || sql.contains(oldName.toUpperCase()) ) ) {
String changedSql = null;
if( sql.contains(oldName) ) {
changedSql = sql.replaceAll(oldName, newName);
} else {
changedSql = sql.replaceAll(oldName.toUpperCase(), newName.toUpperCase());
}
if( !statusWasInvalidated ) {
SqlMappingRootCache.invalidateStatus(mappingRoot, true, txnSource);
}
boolean handled = false;
if (mEditor != null) {
// Yes, check to see if dirty:
ModelObjectEditorPage moep = mEditor.getActiveObjectEditor();
if (moep != null && moep instanceof TransformationObjectEditorPage) {
TransformationObjectEditorPage transOEP = (TransformationObjectEditorPage)moep;
handled = updateSqlEditorPanel(transOEP, QueryValidator.DELETE_TRNS, mappingRoot, changedSql);
}
}
if( !handled ) {
dependentModelChanged = true;
updateSqlInTransaction(mappingRoot, QueryValidator.DELETE_TRNS, changedSql, NOT_SIGNIFICANT, txnSource);
}
}
if( dependentModelChanged ) {
// Check the SQL Alias
Collection<EObject> sqlAliases = ((SqlTransformation)mappingRoot.getHelper()).getAliases();
SqlAlias oldAlias = null;
for( EObject sqlAlias : sqlAliases ) {
if( sqlAlias instanceof SqlAlias ) {
if( ((SqlAlias)sqlAlias).getAlias().equalsIgnoreCase(oldName)) {
oldAlias = (SqlAlias)sqlAlias;
break;
}
}
}
if( oldAlias != null ) {
swapAliasInTransaction(mappingRoot, oldAlias, newName);
}
}
}
}
if( dependentModelChanged) {
changedMRs.add(modelResource);
}
}
}
}
} catch (ModelWorkspaceException ex) {
Util.log(IStatus.ERROR, ex, ex.getMessage());
}
}
}
if( !changedMRs.isEmpty() ) {
for( ModelResource mr: changedMRs) {
try {
rebuildImportsInTransaction(mr.getEmfResource());
mr.save(new NullProgressMonitor(), false);
} catch (ModelWorkspaceException ex) {
Util.log(IStatus.ERROR, ex, ex.getMessage());
}
}
}
}
@Override
public void handleNotifications( Collection notifications, Object txnSource) {
if (!notifications.isEmpty()) {
Collection functionObjectChanges = getModelChangeNotifications(notifications, txnSource);
if (!functionObjectChanges.isEmpty()) {
handleModelChangeNotification(functionObjectChanges, txnSource);
}
}
}
@Override
public void processModelResourceEvent(ModelResourceEvent event) {
if( ModelIdentifier.isFunctionModel(event.getModelResource())) {
if( event.getType() == ModelResourceEvent.RELOADED ||
event.getType() == ModelResourceEvent.CHANGED ||
event.getType() == ModelResourceEvent.REMOVED) {
Set<ModelResource> affectedDependentModelResources = new HashSet<ModelResource>();
affectedDependentModelResources.addAll(ModelUtilities.getResourcesThatUse(event.getModelResource()));
for( ModelResource modelResource : affectedDependentModelResources ) {
try {
// Process all transformations in the TransformationContainer
final EmfResource emfRes = (EmfResource)modelResource.getEmfResource();
final List transformations = emfRes.getModelContents().getTransformations();
for (Iterator i = transformations.iterator(); i.hasNext();) {
EObject eObj = (EObject)i.next();
if (eObj instanceof SqlTransformationMappingRoot) {
final SqlTransformationMappingRoot mappingRoot = (SqlTransformationMappingRoot)eObj;
SqlMappingRootCache.invalidateStatus(mappingRoot, false, this);
}
}
} catch (ModelWorkspaceException e) {
Util.log(IStatus.ERROR, e, e.getMessage());
}
}
}
}
}
@Override
public boolean shouldHandleChangedObject(Object object) {
return isFunctionModelObject(object);
}
private void updateSqlInTransaction(EObject mappingRoot, int sqlType, String sql, boolean significant, Object txnSource) {
// Update the sql and reconcile the attributes in one transaction
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT, false, "Update SQL on function object change", this); //$NON-NLS-1$
boolean succeeded = false;
try {
switch( sqlType ) {
case QueryValidator.SELECT_TRNS: {
TransformationHelper.setSelectSqlString(mappingRoot, sql, NOT_SIGNIFICANT, txnSource);
} break;
case QueryValidator.INSERT_TRNS: {
TransformationHelper.setInsertSqlString(mappingRoot, sql, NOT_SIGNIFICANT, txnSource);
} break;
case QueryValidator.UPDATE_TRNS: {
TransformationHelper.setUpdateSqlString(mappingRoot, sql, NOT_SIGNIFICANT, txnSource);
} break;
case QueryValidator.DELETE_TRNS: {
TransformationHelper.setDeleteSqlString(mappingRoot, sql, NOT_SIGNIFICANT, txnSource);
} break;
}
succeeded = true;
} finally {
// If we started Txn, commit it
if (requiredStart) {
if (succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
private boolean updateSqlEditorPanel(TransformationObjectEditorPage transOEP, int sqlType, EObject mappingRoot, String changedSql) {
if( transOEP.getCurrentMappingRoot() == mappingRoot ) {
// Close editor, then re-open it.
int panelType = transOEP.getCurrentSqlEditor().getPanelType();
switch( sqlType ) {
case QueryValidator.SELECT_TRNS: {
if( panelType == UiConstants.SQLPanels.SELECT || panelType == UiConstants.SQLPanels.UPDATE_SELECT ) {
updateSqlTextInTransaction(transOEP, mappingRoot, changedSql);
return true;
}
} break;
case QueryValidator.INSERT_TRNS: {
if( panelType == UiConstants.SQLPanels.UPDATE_INSERT ) {
updateSqlTextInTransaction(transOEP, mappingRoot, changedSql);
return true;
}
} break;
case QueryValidator.UPDATE_TRNS: {
if( panelType == UiConstants.SQLPanels.UPDATE_UPDATE ) {
updateSqlTextInTransaction(transOEP, mappingRoot, changedSql);
return true;
}
} break;
case QueryValidator.DELETE_TRNS: {
if( panelType == UiConstants.SQLPanels.UPDATE_DELETE ) {
updateSqlTextInTransaction(transOEP, mappingRoot, changedSql);
return true;
}
} break;
}
}
return false;
}
private void updateSqlTextInTransaction(TransformationObjectEditorPage transOEP, EObject mappingRoot, String changedSql) {
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT, false, "Update SQL on function object change", this); //$NON-NLS-1$
boolean succeeded = false;
try {
transOEP.getCurrentSqlEditor().setText(changedSql);
transOEP.updateMessagePanel();
succeeded = true;
} finally {
// If we started Txn, commit it
if (requiredStart) {
if (succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
private void swapAliasInTransaction(EObject mappingRoot, SqlAlias oldAlias, String newName) {
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT, false, "SQL Alias on function object change", this); //$NON-NLS-1$
boolean succeeded = false;
try {
EObject target = oldAlias.getAliasedObject();
boolean removed = TransformationHelper.removeSourceAlias(mappingRoot, target, oldAlias.getAlias(), false, this);
if( removed ) {
TransformationHelper.addSqlAlias(mappingRoot, target, newName, false, this);
}
succeeded = true;
} finally {
// If we started Txn, commit it
if (requiredStart) {
if (succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
private void rebuildImportsInTransaction(final Resource resource) {
boolean requiredStart = ModelerCore.startTxn(NOT_SIGNIFICANT, false, "Rebuild Imports", this); //$NON-NLS-1$
boolean succeeded = false;
try {
ModelBuildUtil.rebuildImports(resource, true);
succeeded = true;
} finally {
// If we started Txn, commit it
if (requiredStart) {
if (succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
private boolean isSourceModelWithPushdownFunction(ModelResource modelResource) {
try {
String uri = modelResource.getPrimaryMetamodelUri();
if( ! RelationalPackage.eNS_URI.equalsIgnoreCase(uri) &&
! (modelResource.getModelType() == ModelType.PHYSICAL_LITERAL) ) {
return false;
}
final ModelObjectCollector moc = new ModelObjectCollector(modelResource.getEmfResource());
for( Object eObj : moc.getEObjects()){
if( eObj instanceof Procedure ) {
if( ((Procedure)eObj).isFunction()) {
return true;
}
}
}
} catch (ModelWorkspaceException ex) {
Util.log(IStatus.ERROR, ex, ex.getMessage());
}
return false;
}
private boolean isFunction(EObject eObject) {
if( eObject instanceof ScalarFunction ) {
return true;
} else if( eObject instanceof Procedure &&
((Procedure)eObject).isFunction()) {
return true;
}
return false;
}
}