/* * 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.extension.definition; import static org.teiid.designer.extension.ExtensionPlugin.Util; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.osgi.util.NLS; import org.teiid.core.designer.HashCodeUtil; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.CoreStringUtil; import org.teiid.designer.extension.ExtensionConstants; import org.teiid.designer.extension.Messages; import org.teiid.designer.extension.properties.ModelExtensionPropertyDefinition; import org.teiid.designer.extension.properties.NamespaceProvider; /** * A <code>ModelExtensionDefinition</code> defines extension properties for metaclasses within a metamodel. * * @since 8.0 */ public class ModelExtensionDefinition implements NamespaceProvider, PropertyChangeListener { /** * The model extension assistant (never <code>null</code>). */ private final ModelExtensionAssistant assistant; /** * Indicates if this MED is a built-in and therefore should not be unregistered. Defaults to {@value} . */ private boolean builtIn; /** * Indicates if this MED has been imported from teiid. Imported can be unregistered but cannot be edited. Defaults to {@value} . */ private boolean imported; /** * The registered property change listeners (never <code>null</code>). */ private final CopyOnWriteArrayList<PropertyChangeListener> listeners; /** * Key is metaclass name, value is a collection of property definitions. */ private final Map<String, Collection<ModelExtensionPropertyDefinition>> properties; /** * The ModelExtensionDefinitionHeader of this definition (never <code>null</code>). */ private final ModelExtensionDefinitionHeader header; /** * @param assistant */ public ModelExtensionDefinition( ModelExtensionAssistant assistant ) { CoreArgCheck.isNotNull(assistant, "assistant is null"); //$NON-NLS-1$ this.assistant = assistant; this.properties = new HashMap<String, Collection<ModelExtensionPropertyDefinition>>(); this.listeners = new CopyOnWriteArrayList<PropertyChangeListener>(); this.header = new ModelExtensionDefinitionHeader(); this.header.addListener(this); } /** * @param assistant the model extension assist (cannot be <code>null</code>) * @param namespacePrefix the unique namespace prefix (can be <code>null</code> or empty) * @param namespaceUri the unique namespace URI (can be <code>null</code> or empty) * @param metamodelUri the metamodel URI that is being extended (can be <code>null</code> or empty) * @param description the description of the definition (can be <code>null</code> or empty) * @param version the definition version (can be <code>null</code> or empty) */ public ModelExtensionDefinition( ModelExtensionAssistant assistant, String namespacePrefix, String namespaceUri, String metamodelUri, String description, String version ) { this(assistant); int versionNumber = ModelExtensionDefinitionHeader.DEFAULT_VERSION; if (!CoreStringUtil.isEmpty(version)) { try { versionNumber = Integer.parseInt(version); this.header.setVersion(versionNumber); } catch (Exception e) { ExtensionConstants.UTIL.log(NLS.bind(Messages.invalidDefinitionFileNewVersion, namespacePrefix, version)); } } this.header.setNamespacePrefix(namespacePrefix); this.header.setNamespaceUri(namespaceUri); this.header.setMetamodelUri(metamodelUri); this.header.setDescription(description); } /** * @param listener the listener being added (cannot be <code>null</code>) * @return <code>true</code> if the listener was successfully added */ public boolean addListener( PropertyChangeListener listener ) { CoreArgCheck.isNotNull(listener, "listener is null"); //$NON-NLS-1$ boolean added = this.listeners.addIfAbsent(listener); if (added) { added = this.header.addListener(listener); } return added; } /** * @param metaclassName the name of the metaclass that can be extended (never <code>null</code> or empty) * @return <code>true</code> if the metaclass was added */ public boolean addMetaclass( String metaclassName ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is empty"); //$NON-NLS-1$ if (this.properties.containsKey(metaclassName)) { return false; } this.properties.put(metaclassName, new ArrayList<ModelExtensionPropertyDefinition>()); notifyChangeListeners(PropertyName.METACLASS, null, metaclassName); return true; } /** * @param modelType the model type being added (cannot be <code>null</code> or empty) * @return <code>true</code> if the model type was added */ public boolean addModelType( String modelType ) { return getHeader().addModelType(modelType); } /** * @param metaclassName the metaclass name to which the extension property definition pertains to (cannot be <code>null</code> * or empty) * @param propDefn the property definition being added (cannot be <code>null</code>) * @return <code>true</code> if the property definition was successfully added to the metaclass name */ public boolean addPropertyDefinition( String metaclassName, ModelExtensionPropertyDefinition propDefn ) { CoreArgCheck.isNotNull(propDefn, "propDefn is null"); //$NON-NLS-1$ Collection<ModelExtensionPropertyDefinition> props = internalGetProperties(metaclassName); if (props == null) { if (addMetaclass(metaclassName)) { props = internalGetProperties(metaclassName); } else { return false; } } // add new property and alert listeners if (props.contains(propDefn)) { return false; } // Check if props contains a propDefin with the same namespace and ID, then return false for( ModelExtensionPropertyDefinition defn : props ) { if( defn.getNamespaceProvider().getNamespacePrefix().equals(propDefn.getNamespaceProvider().getNamespacePrefix()) && defn.getId().equals(propDefn.getId()) ) { return false; } } props.add(propDefn); notifyChangeListeners(PropertyName.PROPERTY_DEFINITION, null, propDefn); return true; } /** * @param metaclassName the name of the metaclass that the properties will be added to (cannot be <code>null</code> or empty) * @param propDefns the property definitions being added (cannot be <code>null</code>) * @return <code>true</code> if one or more property definitions were added */ private boolean addPropertyDefinitions( String metaclassName, Collection<ModelExtensionPropertyDefinition> propDefns ) { CoreArgCheck.isNotNull(propDefns, "propDefns is null"); //$NON-NLS-1$ boolean added = false; for (ModelExtensionPropertyDefinition propDefn : propDefns) { if (addPropertyDefinition(metaclassName, propDefn)) { added = true; } } return added; } /** * @param metaclassName the metaclass name being checked (cannot be <code>null</code> or empty) * @return <code>true</code> if this definition has extension properties for the specified metaclass name */ public boolean extendsMetaclass( String metaclassName ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is null"); //$NON-NLS-1$ return this.properties.containsKey(metaclassName); } /** * @param metamodelUri the metamodel URI being checked (cannot be <code>null</code> or empty) * @return <code>true</code> if this definition extends the specified metamodel URI */ public boolean extendsMetamodelUri( String metamodelUri ) { CoreArgCheck.isNotEmpty(metamodelUri, "metamodelUri is null"); //$NON-NLS-1$ return getMetamodelUri().equals(metamodelUri); } /** * @return the description (can be <code>null</code> or empty) */ public String getDescription() { return this.header.getDescription(); } /** * @return the metaclass names that have extended properties defined (never <code>null</code>) */ public String[] getExtendedMetaclasses() { return this.properties.keySet().toArray(new String[this.properties.size()]); } /** * @return the metamodel URI (can be <code>null</code> or empty) */ public String getMetamodelUri() { return this.header.getMetamodelUri(); } /** * @return the model extension assistant (never <code>null</code>) */ public ModelExtensionAssistant getModelExtensionAssistant() { return this.assistant; } /** * {@inheritDoc} * * @see org.teiid.designer.extension.properties.NamespaceProvider#getNamespacePrefix() */ @Override public String getNamespacePrefix() { return this.header.getNamespacePrefix(); } /** * {@inheritDoc} * * @see org.teiid.designer.extension.properties.NamespaceProvider#getNamespaceUri() */ @Override public String getNamespaceUri() { return this.header.getNamespaceUri(); } /** * @return the header (never <code>null</code> or empty) */ public ModelExtensionDefinitionHeader getHeader() { return this.header; } /** * @param metaclassName the metaclass name whose extended property is being requested (cannot be <code>null</code> or empty) * @param propId the identifier of the property definition being requested (cannot be <code>null</code> or empty) * @return the property definition or <code>null</code> if not found */ public ModelExtensionPropertyDefinition getPropertyDefinition( String metaclassName, String propId ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is empty"); //$NON-NLS-1$ CoreArgCheck.isNotEmpty(propId, "propId is empty"); //$NON-NLS-1$ // make sure property ID is one for this definition if (!ModelExtensionPropertyDefinition.Utils.isExtensionPropertyId(propId, this)) { return null; } Collection<ModelExtensionPropertyDefinition> props = internalGetProperties(metaclassName); // definition does not have any properties for that metaclass if (props == null) { return null; } // find property for (ModelExtensionPropertyDefinition propDefn : props) { if (propId.equals(propDefn.getId())) { return propDefn; } } // not found return null; } /** * @return a copy of the collection of extension property definitions (never <code>null</code> but can be empty) */ public Map<String, Collection<ModelExtensionPropertyDefinition>> getPropertyDefinitions() { Map<String, Collection<ModelExtensionPropertyDefinition>> properties = new HashMap<String, Collection<ModelExtensionPropertyDefinition>>(); for (Map.Entry<String, Collection<ModelExtensionPropertyDefinition>> entry : this.properties.entrySet()) { Collection<ModelExtensionPropertyDefinition> propDefns = new ArrayList<ModelExtensionPropertyDefinition>(); propDefns.addAll(entry.getValue()); String metaclassName = entry.getKey(); properties.put(metaclassName, propDefns); } return properties; } /** * @param metaclassName the metaclass name whose extended property definitions are being requested (cannot be <code>null</code> * or empty) * @return the extension property definitions (never <code>null</code> but can be empty) */ public Collection<ModelExtensionPropertyDefinition> getPropertyDefinitions( String metaclassName ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is null"); //$NON-NLS-1$ Collection<ModelExtensionPropertyDefinition> props = internalGetProperties(metaclassName); if (props == null) { return Collections.emptyList(); } return props; } /** * If an empty collection is returned then all model types are supported. * * @return an unmodifiable collection of supported model types (never <code>null</code> but can be empty) */ public Set<String> getSupportedModelTypes() { return getHeader().getSupportedModelTypes(); } /** * @return the version (a positive integer) */ public int getVersion() { return this.header.getVersion(); } /** * @param metaclassName the metaclass name whose extension properties are being requested (cannot be <code>null</code>) * @return the extension properties (never <code>null</code>) */ private Collection<ModelExtensionPropertyDefinition> internalGetProperties( String metaclassName ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is null"); //$NON-NLS-1$ return this.properties.get(metaclassName); } /** * @return <code>true</code> if this MED is a built-in and therefore should not be unregistered */ public boolean isBuiltIn() { return this.builtIn; } /** * Marks this MED as being a built-in and therefore cannot be unregistered. */ public void markAsBuiltIn() { this.builtIn = true; } /** * @return <code>true</code> if this MED has been imported */ public boolean isImported() { return this.imported; } /** * Marks this MED as being imported from a teiid instance. */ public void markAsImported() { this.imported = true; } /** * Broadcasts the property change to all registered listeners. * * @param property the property that has been changed (cannot be <code>null</code>) * @param oldValue the old value (can be <code>null</code>) * @param newValue (can be <code>null</code>) */ private void notifyChangeListeners( final PropertyName property, final Object oldValue, final Object newValue ) { PropertyChangeEvent event = new PropertyChangeEvent(this, property.toString(), oldValue, newValue); for (final Object listener : this.listeners.toArray()) { try { ((PropertyChangeListener)listener).propertyChange(event); } catch (Exception e) { Util.log(e); this.listeners.remove(listener); } } } /** * {@inheritDoc} * * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) */ @Override public void propertyChange( PropertyChangeEvent e ) { if (PropertyName.METAMODEL_URI.toString().equals(e.getPropertyName())) { // delete all metaclasses and properties String[] metaclasses = getExtendedMetaclasses(); if (metaclasses.length != 0) { this.properties.clear(); notifyChangeListeners(PropertyName.METACLASS, metaclasses, null); } } } /** * @param listener the listener being removed (cannot be <code>null</code>) * @return <code>true</code> if the listener was successfully removed */ public boolean removeListener( PropertyChangeListener listener ) { CoreArgCheck.isNotNull(listener, "listener is null"); //$NON-NLS-1$ boolean removed = this.listeners.remove(listener); if (removed) { removed = this.header.removeListener(listener); } return removed; } /** * @param modelType the model type being removed (cannot be <code>null</code> or empty) * @return <code>true</code> if the model type was removed */ public boolean removeModelType( String modelType ) { return getHeader().removeModelType(modelType); } /** * @param metaclassName the name of the metaclass whose property definition is being removed (cannot be <code>null</code> or * empty) * @param propDefn the property definition being removed (cannot be <code>null</code>) * @return <code>true</code> if the property definition has been removed */ public boolean removePropertyDefinition( String metaclassName, ModelExtensionPropertyDefinition propDefn ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is empty"); //$NON-NLS-1$ CoreArgCheck.isNotNull(propDefn, "propDefn is null"); //$NON-NLS-1$ Collection<ModelExtensionPropertyDefinition> propDefns = getPropertyDefinitions(metaclassName); boolean removed = propDefns.remove(propDefn); if (removed) { notifyChangeListeners(PropertyName.PROPERTY_DEFINITION, propDefn, null); } return removed; } /** * @param metaclassName the name of the metaclass being removed (cannot be <code>null</code> or empty) * @return the property definitions of the metaclass being removed (can be <code>null</code> if metaclass was not found or * didn't have properties) */ public Collection<ModelExtensionPropertyDefinition> removeMetaclass( String metaclassName ) { CoreArgCheck.isNotEmpty(metaclassName, "metaclassName is empty"); //$NON-NLS-1$ if (this.properties.containsKey(metaclassName)) { Collection<ModelExtensionPropertyDefinition> propDefns = this.properties.remove(metaclassName); notifyChangeListeners(PropertyName.METACLASS, metaclassName, null); return propDefns; } // metaclass not found return null; } /** * @param newDescription the new description (can be <code>null</code> or empty) */ public void setDescription( String newDescription ) { this.header.setDescription(newDescription); } /** * @param newMetamodelUri the new metamodel URI (can be <code>null</code> or empty) */ public void setMetamodelUri( String newMetamodelUri ) { this.header.setMetamodelUri(newMetamodelUri); } /** * @param newNamespacePrefix the new namespace prefix (can be <code>null</code> or empty) */ public void setNamespacePrefix( String newNamespacePrefix ) { this.header.setNamespacePrefix(newNamespacePrefix); } /** * @param newNamespaceUri the new namespace URI (can be <code>null</code> or empty) */ public void setNamespaceUri( String newNamespaceUri ) { this.header.setNamespaceUri(newNamespaceUri); } /** * If the new version is an invalid version number the version remains unchanged. * * @param newVersion the new version */ public void setVersion( int newVersion ) { this.header.setVersion(newVersion); } /** * Changes the original metaclass name to the new name and keeps all the property definitions. * * @param originalMetaclass the name of the metaclass that is being changed (cannot be <code>null</code> or empty) * @param newMetaclass the new name of the metaclass (cannot be <code>null</code> or empty) */ public void updateMetaclass( String originalMetaclass, String newMetaclass ) { CoreStringUtil.isEmpty(originalMetaclass); CoreStringUtil.isEmpty(newMetaclass); Collection<ModelExtensionPropertyDefinition> propDefns = removeMetaclass(originalMetaclass); if ((propDefns == null) || propDefns.isEmpty()) { addMetaclass(newMetaclass); } else { addPropertyDefinitions(newMetaclass, propDefns); } } /** * {@inheritDoc} * * @see java.lang.Object#equals(java.lang.Object) */ @Override public final boolean equals( final Object object ) { if (this == object) return true; if (object == null) return false; if (getClass() != object.getClass()) return false; final ModelExtensionDefinition other = (ModelExtensionDefinition)object; // Check MED headers equal if (!getHeader().equals(other.getHeader())) { return false; } // Verify same number of extended metaclasses String[] extendedMetaclassNames = this.getExtendedMetaclasses(); String[] otherMetaclassNames = other.getExtendedMetaclasses(); if (extendedMetaclassNames.length != otherMetaclassNames.length) return false; if (extendedMetaclassNames.length == 0 && otherMetaclassNames.length == 0) return true; // Check that metaClasses extended are same, and Property Definitions are the same for each boolean areEqual = true; for (int i = 0; i < extendedMetaclassNames.length; i++) { Set<ModelExtensionPropertyDefinition> metaClassPropertyDefns = new HashSet<ModelExtensionPropertyDefinition>(this.getPropertyDefinitions(extendedMetaclassNames[i])); Set<ModelExtensionPropertyDefinition> otherMetaClassPropertyDefns = new HashSet<ModelExtensionPropertyDefinition>(other.getPropertyDefinitions(extendedMetaclassNames[i])); // If sets are different size, or they are equal size (not empty) but dont contain the same elements - not equal if ((metaClassPropertyDefns.size() != otherMetaClassPropertyDefns.size()) || (!metaClassPropertyDefns.isEmpty() && !metaClassPropertyDefns.containsAll(otherMetaClassPropertyDefns))) { areEqual = false; break; } } return areEqual; } /** * {@inheritDoc} * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int result = HashCodeUtil.hashCode(0, this.builtIn); result = HashCodeUtil.hashCode(result, getHeader()); String[] extendedMetaclassNames = this.getExtendedMetaclasses(); for (int i = 0; i < extendedMetaclassNames.length; i++) { Collection<ModelExtensionPropertyDefinition> metaclassPropertyDefns = this.getPropertyDefinitions(extendedMetaclassNames[i]); for (ModelExtensionPropertyDefinition propDefn : metaclassPropertyDefns) { result = HashCodeUtil.hashCode(result, propDefn); } } return result; } /** * @param modelType the model type being checked (cannot be <code>null</code>) * @return <code>true</code> if the model type is supported by the MED */ public boolean supportsModelType( String modelType ) { return getHeader().supportsModelType(modelType); } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return getHeader().toString(); } /** * The properties that can be changed. */ public enum PropertyName { /** * A property definition. */ PROPERTY_DEFINITION, /** * The description. */ DESCRIPTION, /** * The metamodel URI. */ METAMODEL_URI, /** * The metaclass name being extended. */ METACLASS, /** * The list of applicable model types. */ MODEL_TYPES, /** * The namespace prefix. */ NAMESPACE_PREFIX, /** * The namespace URI. */ NAMESPACE_URI, /** * The version. */ VERSION } }