/* * 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.metamodels.builder.execution; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcoreFactory; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.I18nUtil; import org.teiid.designer.metamodels.builder.MetamodelEntityBuilder; import org.teiid.designer.metamodels.builder.MetamodelEntityRecord; import org.teiid.designer.metamodels.builder.execution.util.MetamodelBuilderUtil; import org.teiid.designer.metamodels.builder.util.MetaClassUriHelper; import org.teiid.designer.metamodels.core.Annotation; import org.teiid.designer.metamodels.core.AnnotationContainer; import org.teiid.designer.metamodels.core.CorePackage; import org.teiid.designer.metamodels.core.extension.util.ExtensionUtil; import org.teiid.designer.metamodels.relational.util.RelationalUtil; /** * Implementation of the MetamodelEntityBuilder - creates EObjects given a MetamodelEntityRecord or List of records. * * @since 8.0 */ public class MetamodelEntityBuilderImpl implements MetamodelEntityBuilder, MetamodelBuilderConstants { // Instance variables private final MultiStatus status; private final ResourceSet resources; private boolean builderDebugEnabled = false; private static final String ENTITY_NAME = "Name"; //$NON-NLS-1$ private MetamodelEntityRecord currentRecord; private static final String I18N_PREFIX = I18nUtil.getPropertyPrefix(MetamodelEntityBuilderImpl.class); private static String getString( final String id ) { return UTIL.getString(I18N_PREFIX + id); } private static String getString( final String id, final Object param1 ) { return UTIL.getString(I18N_PREFIX + id, param1); } private static String getString( final String id, final Object param1, final Object param2 ) { return UTIL.getString(I18N_PREFIX + id, param1, param2); } // ================================================================================== // C O N S T R U C T O R S // ================================================================================== /** * Constructor * * @param status - The MultiStatus object used to accumulate errors and warnings during build - May not be null * @param resources - The resourceSet where entities will be added (internal ojbect resources must be contained in the * resource set or they will not be found) - May not be null or empty */ public MetamodelEntityBuilderImpl( final MultiStatus status, final ResourceSet resources ) { CoreArgCheck.isNotNull(status); CoreArgCheck.isNotNull(resources); CoreArgCheck.isNotEmpty(resources.getResources()); this.status = status; this.resources = resources; } /** * Create a new EObject using the given record and then adds the entity to the appropriate parent and sets all supplied * feature values. * * @param record - The MetamodelEntityRecord to use to drive creation - May not be null * @since 4.3 */ @Override public EObject create( MetamodelEntityRecord record, IProgressMonitor monitor ) { CoreArgCheck.isNotNull(record); this.currentRecord = record; EObject entity = null; boolean isExisting = false; // --------------------------------------------------------- // First see if entity with desired name already exists // --------------------------------------------------------- String parentPath = record.getParentPath(); // Get the name feature first, if it exists. final Map featuresMap = record.getFeaturesNameValueMap(); final Object nameValue = featuresMap.get(ENTITY_NAME); if (nameValue != null) { String shortName = (String)nameValue; String fullPath = parentPath + "\\\\" + shortName; //$NON-NLS-1$ final Object existingEntity = MetamodelBuilderUtil.findEObjectByPath(resources, fullPath, record.getMetaClassUri(), status); if (existingEntity != null && existingEntity instanceof EObject) { entity = (EObject)existingEntity; isExisting = true; } } // Get the metaClass Uri and break it apart String metaClassUri = record.getMetaClassUri(); String ePkgUri = MetaClassUriHelper.getPackageUri(metaClassUri); // Get the ePackage (May not be null) EPackage ePkg = MetamodelBuilderUtil.getEPackageForUri(ePkgUri, status); if (ePkg == null) { return null; } // Get the eClass (May not be null) EClass eClass = (EClass)MetamodelBuilderUtil.getEClassForUri(metaClassUri, status); if (eClass == null) { return null; } // --------------------------------------------------------- // If existing entity was not found, try to create one // --------------------------------------------------------- // Create the entity - Log if unable to create an entity EFactory factory = ePkg.getEFactoryInstance(); if (entity == null) { entity = factory.create(eClass); } if (entity == null) { final String msg = getString("unableToCreateEntity", eClass.getName()); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } return null; } // Process the information in the entity record and return the new object process(entity, record, factory, isExisting); if (monitor != null) { monitor.worked(1); } return entity; } /** * Create new EObjects using the given records. Adds the entities to the appropriate parent and sets all supplied feature * values. * * @param records - the list of MetamodelEntityRecords to use to drive creation - May not be null * @since 4.3 */ @Override public List create( List records, IProgressMonitor monitor ) { CoreArgCheck.isNotNull(records); List eObjs = new ArrayList(records.size()); Iterator iter = records.iterator(); while (iter.hasNext()) { EObject eObj = create((MetamodelEntityRecord)iter.next(), monitor); if (eObj != null) { eObjs.add(eObj); } } return eObjs; } // ================================================================================== // H E L P E R M E T H O D S // ================================================================================== /* * Process the information in the entity record for the given EObject * @param - entity - The EObject to process * @param - entityRecord - The MetamodelEntityRecord information to use to populate * the new entity. */ private void process( final EObject entity, final MetamodelEntityRecord entityRecord, EFactory factory, boolean isExisting ) { if (!isExisting) { // Add the entity to a parent or resource first boolean added = addEntity(entity, entityRecord); if (!added) { return; } } // Set the name feature first, if it exists. Makes subsequent error messages more readable... final Map featuresMap = entityRecord.getFeaturesNameValueMap(); if (!isExisting) { final Object nameValue = featuresMap.get(ENTITY_NAME); if (entity != null && nameValue != null) { setFeatureValue(entity, factory, ENTITY_NAME, nameValue, isExisting); } } // Process all the remaining feature name / value pairs (dont do 'name' again) final Iterator properties = featuresMap.entrySet().iterator(); while (properties.hasNext()) { final Map.Entry next = (Map.Entry)properties.next(); final String name = (String)next.getKey(); final Object value = next.getValue(); if (name != null && entity != null && !name.equals(ENTITY_NAME)) { setFeatureValue(entity, factory, name, value, isExisting); } } } /* * Add the given entity to the appropriate parent (or resource if a root object) * @param entity - EObject to add (guaranteed to be not null) * @param entityRecord - MetamodelEntityRecord to drive addition * @return - true if success * @since 4.3 */ private boolean addEntity( final EObject entity, final MetamodelEntityRecord entityRecord ) { final String path = entityRecord.getParentPath(); CoreArgCheck.isNotNull(path); // Find the rsrc using the path info - Log if null final Resource rsrc = MetamodelBuilderUtil.findResource(resources, path); if (rsrc == null) { final String msg = getString("noRsrc", entityRecord.getParentPath()); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } return false; } // Find the parent using the path info - Log if null (root objects will return the resource) final Object parent = MetamodelBuilderUtil.findEObjectByPath(resources, path, entityRecord.getParentMetaClassUri(), status); if (parent == null) { final String msg = getString("noParent", entity.eClass().getName(), entityRecord.getParentPath()); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } return false; } // Add the entity as a child to the given parent (parent may be EObject or a Resource) return addChildToParent(rsrc, entity, parent); } /* * Add the given Child to the given Parent * @param rsrc - Resource both objects exist * @param child - Child EObject * @param parent - Parent object (may be Resource for roots, else EObject) * @return true if success * @since 4.3 */ private boolean addChildToParent( final Resource rsrc, final EObject child, final Object parent ) { // Simple case... just add entity as a root. if (parent instanceof Resource) { ((Resource)parent).getContents().add(child); return true; } // Find the correct utility class to use for adding the given child to the given parent // by primary metamodel information. final EObject parentEObj = (EObject)parent; final int rsrcType = MetamodelBuilderUtil.getModelType(rsrc); boolean added = false; switch (rsrcType) { case RELATIONAL_MODEL: { added = RelationalUtil.addChildToParent(child, parentEObj); break; } case EXTENSION_MODEL: { added = ExtensionUtil.addChildToParent(child, parentEObj); } } // Log any failures if (!added) { final String msg = getString("noAdd", child.eClass().getName(), parentEObj.eClass().getName()); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } } return added; } /* * For a multiValued feature add the new value to the existing list of values * @param eObject - Feature owner * @param featureName - name of feature to add new value * @param value - value to add to feature * @return - true of success * @since 4.3 */ private boolean addFeatureValue( final EObject eObject, final String featureName, final Object value ) { // find the feature... return false if no feature found EStructuralFeature feature = getFeature(eObject.eClass(), featureName, false); if (feature != null && feature.isMany()) { List values = (List)eObject.eGet(feature); if (value instanceof Collection) { return values.addAll((Collection)value); } return values.add(value); } return false; } /* * Set the value of a given feautre * @param eObject - Feature owner * @param featureName - Name of feature to set * @param value - Value to set on feature * @since 4.3 */ private void setFeatureValue( final EObject eObject, final EFactory factory, final String featureName, final Object value, final boolean isExisting ) { // Since we are only doing creates, should not need to set null values. If null // value is encountered, skip the set operation altogether. We may need to do // 'unSet' if skipping it doesn't work out. if (value == null && !isExisting) { return; } // This is one of the most risky operations to perform generically. // Wrap in a try catch block to ensure we can log a meaningful message on failure. try { // Special case for description if (featureName != null && featureName.equalsIgnoreCase(DESCRIPTION)) { setDescription(eObject, (String)value); return; } // Find the feature EStructuralFeature feature = getFeature(eObject.eClass(), featureName, false); if (feature == null) { // Look for extension EObject extension = MetamodelBuilderUtil.getExtension(eObject, status); if (extension != null) { // Found an extension... recall with ObjectExtension instead of the // Extended EObject. setFeatureValue(extension, factory, featureName, value, isExisting); return; } // Could not find feature OR extension... log and return; final String msg = getString("noFeature", featureName, getEntityString(this.currentRecord, eObject)); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } return; } // If the feature is not changeable, log message and return if (!feature.isChangeable()) { // Could not find feature OR extension... log and return; final String msg = getString("featureNotChangeable", featureName, getEntityString(this.currentRecord, eObject)); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.WARNING, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } return; } // All references are passed in as comma delimited paths if (feature instanceof EReference && value instanceof String) { final Collection refObjects = new ArrayList(); final StringTokenizer refs = new StringTokenizer((String)value, ","); //$NON-NLS-1$ Object first = null; while (refs.hasMoreTokens()) { // resolve each path final String ref = refs.nextToken(); final Object next = MetamodelBuilderUtil.findEObjectByPath(resources, ref, null, status); if (next != null) { refObjects.add(next); if (first == null) { first = next; } } else { final String msg = getString("badRef", ref, featureName); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } } } // If we only have one entity, don't use the collection as not all // refs are multivalued. if (refObjects.size() == 1) { setFeatureValue(eObject, factory, featureName, first, isExisting); } else if (refObjects.size() > 1) { setFeatureValue(eObject, factory, featureName, refObjects, isExisting); } } else if (!feature.isMany()) { // Convert the String value to the correct EDataType if (value instanceof String && feature instanceof EAttribute) { final EDataType type = ((EAttribute)feature).getEAttributeType(); if (type instanceof EEnum) { Object valObj = factory.createFromString(type, (String)value); eObject.eSet(feature, valObj); } else { Object dtValue = EcoreFactory.eINSTANCE.createFromString(type, (String)value); eObject.eSet(feature, dtValue); } } else if (value instanceof Character && feature instanceof EAttribute) { final EDataType type = ((EAttribute)feature).getEAttributeType(); if (type.getName().equalsIgnoreCase("EBoolean")) { //$NON-NLS-1$ Object val = getBooleanForChar((Character)value); eObject.eSet(feature, val); } } else { eObject.eSet(feature, value); } } else if (feature.isMany()) { // Use logic for adding values to multivalued features if (value != null) { addFeatureValue(eObject, featureName, value); } else { // Cannot add null to multiValued feature final String msg = getString("addNullToMultiFeature", featureName, getEntityString(this.currentRecord, eObject)); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.WARNING, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.WARNING, msg); } } } } catch (Exception err) { // Log any failures - final String msg = getString("errFeature", featureName, getEntityString(this.currentRecord, eObject)); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg, err); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } } } /* * Helper method for converting character to the appropriate Boolean * @param character - the character to interpret as a Boolean * @return the Boolean value for the supplied character */ private Boolean getBooleanForChar( Character character ) { Boolean result = null; char charValue = character.charValue(); if (charValue == 't' || charValue == 'T' || charValue == '1' || charValue == 'Y' || charValue == 'y') { result = Boolean.TRUE; } else if (charValue == 'f' || charValue == 'F' || charValue == '0' || charValue == 'N' || charValue == 'n') { result = Boolean.FALSE; } return result; } /* * Helper method for getting a readable string for the current EObject being processed, for * more readable error messages. The string is of the format "Type:path\\objectName", for example * "Column MyModel\\Catalog\\MyTable\\ColumnA". * @param entityRecord the entity record being processed * @param eObject the eObject being processed * @return the readable string */ private String getEntityString( MetamodelEntityRecord entityRecord, EObject eObject ) { StringBuffer sb = new StringBuffer(); if (eObject != null) { EClass ec = eObject.eClass(); // append class type to buffer sb.append(ec.getName() + " "); //$NON-NLS-1$ // add the full name of the object including path, if possible if (entityRecord != null && entityRecord.getParentPath() != null) { sb.append(entityRecord.getParentPath() + "\\\\"); //$NON-NLS-1$ } else { sb.append("UnknownPath" + "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$ } // add the eObject name value if available EStructuralFeature nameFeature = getFeature(ec, ENTITY_NAME, false); if (nameFeature != null) { final Object nameValue = eObject.eGet(nameFeature); if (nameValue != null) { sb.append(nameValue.toString()); } else { sb.append("UnknownName"); //$NON-NLS-1$ } } } return sb.toString(); } /* * Helper method for setting descriptions on eObjects * @param eObject - entity to set description * @param value - description * @since 4.3 */ private void setDescription( EObject eObject, final String value ) { // TODO should be checking to ensure resource supports annotations - No need in initial implementation. Resource eResource = eObject.eResource(); // Resource may not be null - log failure if (eResource == null) { final String msg = getString("noResource"); //$NON-NLS-1$ MetamodelBuilderUtil.addStatus(status, IStatus.ERROR, msg); if (this.builderDebugEnabled) { MetamodelBuilderExecutionPlugin.Util.log(IStatus.ERROR, msg); } return; } // Get the AnnotationContainer for this resource AnnotationContainer cntr = MetamodelBuilderUtil.getAnnotationContainer(eResource); if (cntr == null) { EFactory factory = CorePackage.eINSTANCE.getEFactoryInstance(); cntr = (AnnotationContainer)factory.create(CorePackage.eINSTANCE.getAnnotationContainer()); eResource.getContents().add(cntr); } // Check if an annotation exists for this object final Annotation existing = cntr.findAnnotation(eObject); if (existing != null) { // Reset the description if the annotation exists existing.setDescription(value); } else { // Create the annotation and add it to the AnnotationContainer EFactory factory = CorePackage.eINSTANCE.getEFactoryInstance(); Annotation annot = (Annotation)factory.create(CorePackage.eINSTANCE.getAnnotation()); annot.setAnnotatedObject(eObject); annot.setDescription(value); annot.setAnnotationContainer(cntr); } } /* * Find the EStructuralFeature with the given name for the given EClass * @param eClass - EClass to search * @param featureName - Name of feature to search for - May not be null * @param caseSensitiveMatch - flag to determine whether name must match case. * @return matching EStructuralFeature * @since 4.3 */ private EStructuralFeature getFeature( final EClass eClass, final String featureName, final boolean caseSensitiveMatch ) { if (featureName == null) { return null; } // Simple case - if case sensitive, just ask the eClass for the featureName if (caseSensitiveMatch) { EStructuralFeature next = eClass.getEStructuralFeature(featureName); if (next != null) { return next; } } // Iterate over the featurs comparing names. for (Iterator iter = eClass.getEAllStructuralFeatures().iterator(); iter.hasNext();) { final EStructuralFeature next = (EStructuralFeature)iter.next(); if (featureName.equalsIgnoreCase(next.getName())) { return next; } } // No match found... return null return null; } }