/* * 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.registry; import static org.teiid.designer.extension.ExtensionPlugin.Util; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; 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.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.StringConstants; import org.teiid.designer.extension.ExtensionConstants; import org.teiid.designer.extension.ExtensionConstants.MxdType; import org.teiid.designer.extension.ExtensionPlugin; import org.teiid.designer.extension.Messages; import org.teiid.designer.extension.definition.ModelExtensionAssistant; import org.teiid.designer.extension.definition.ModelExtensionDefinition; import org.teiid.designer.extension.definition.ModelExtensionDefinitionParser; import org.teiid.designer.extension.definition.ModelObjectExtensionAssistant; import org.teiid.designer.extension.properties.ModelExtensionPropertyDefinition; /** * * * @since 8.0 */ public final class ModelExtensionRegistry { /** * The metamodel URIs that can have extension properties associated with them. */ private Set<String> extendableMetamodelUris; /** * Key is namespace prefix, value is model extension definition. Never <code>null</code>. */ private final Map<String, ModelExtensionDefinition> definitions; /** * A collection of registry listeners (never <code>null</code>). */ private final CopyOnWriteArrayList<RegistryListener> listeners; /** * Key is namespace URI, value is namespace prefix. Never <code>null</code>. */ private final Map<String, String> namespaces; /** * Parser for the *.mxd file. Creates the model extension definition. Never <code>null</code>. */ private ModelExtensionDefinitionParser parser; /** * @param medSchema the model extension definition schema file (cannot be <code>null</code> and must exist) * @throws IllegalStateException if there is a problem with the model extension XSD */ public ModelExtensionRegistry( File medSchema ) throws IllegalStateException { this.definitions = new HashMap<String, ModelExtensionDefinition>(); this.listeners = new CopyOnWriteArrayList<RegistryListener>(); this.namespaces = new HashMap<String, String>(); try { this.parser = new ModelExtensionDefinitionParser(medSchema); } catch (Exception e) { IllegalStateException error; if (e instanceof IllegalStateException) { error = (IllegalStateException)e; } else { error = new IllegalStateException(e); } throw error; } } /** * Add a ModelExtensionDefinition to the Registry * * @param definitionStream the model extension input stream (cannot be <code>null</code>) * @param assistant the model extension assistant (cannot be <code>null</code>) * @return the model extension definition (never <code>null</code>) * @throws Exception if the definition file is <code>null</code> or if there is a problem parsing the file */ public ModelExtensionDefinition addDefinition( InputStream definitionStream, ModelExtensionAssistant assistant ) throws Exception { ModelExtensionDefinition definition = this.parser.parse(definitionStream, assistant); assert definition != null : "parser should not return a null model extension definition"; //$NON-NLS-1$ // parser may have errors Collection<String> errors = this.parser.getErrors(); if (!errors.isEmpty()) { // attach first error message throw new Exception(NLS.bind(Messages.modelExtensionDefinitionHasParseErrors, errors.size(), errors.iterator().next())); } String namespacePrefix = definition.getNamespacePrefix(); // don't allow a namespace prefix that has already been registered if (!definition.isBuiltIn() && this.definitions.containsKey(namespacePrefix)) { ExtensionPlugin.Util.log(IStatus.WARNING, NLS.bind(Messages.namespacePrefixAlreadyRegistered, namespacePrefix)); } String namespaceUri = definition.getNamespaceUri(); // don't allow a namespace URI that has already been registered if (!definition.isBuiltIn() && this.namespaces.containsKey(namespaceUri)) { ExtensionPlugin.Util.log(IStatus.WARNING, NLS.bind(Messages.namespaceUriAlreadyRegistered, namespaceUri)); } // Determine if the definition extends a valid Metamodel String metamodelUri = definition.getMetamodelUri(); if ((this.extendableMetamodelUris == null) || !this.extendableMetamodelUris.contains(metamodelUri)) { throw new Exception(NLS.bind(Messages.invalidMetamodelUriExtension, metamodelUri)); } // add to registry this.definitions.put(namespacePrefix, definition); this.namespaces.put(namespaceUri, namespacePrefix); // notify registry listeners fireEvent(RegistryEvent.createAddDefinitionEvent(definition)); return definition; } /** * Remove a ModelExtensionDefinition from the Registry * * @param namespacePrefix the namespacePrefix of the ModelExtensionDefinition to be removed from the repository. */ public void removeDefinition( String namespacePrefix ) { CoreArgCheck.isNotEmpty(namespacePrefix, "namespacePrefix is empty"); //$NON-NLS-1$ // Remove Definition and namespaceUri if it exists in the registry if (isNamespacePrefixRegistered(namespacePrefix)) { ModelExtensionDefinition removedMed = this.definitions.remove(namespacePrefix); if (removedMed != null) { this.namespaces.remove(removedMed.getNamespaceUri()); // notify registry listeners fireEvent(RegistryEvent.createRemoveDefinitionEvent(removedMed)); } } } /** * @param listener the listener being added * @return <code>true</code> if the listener was successfully added */ public boolean addListener( RegistryListener listener ) { CoreArgCheck.isNotNull(listener, "listener is null"); //$NON-NLS-1$ if (this.listeners.contains(listener)) return false; return this.listeners.add(listener); } /** * @param event the event being broadcast (cannot be <code>null</code>) */ private void fireEvent( RegistryEvent event ) { assert event != null : "event is null"; //$NON-NLS-1$ for (RegistryListener listener : this.listeners) { try { listener.process(event); } catch (Exception e) { // don't let one listener exception stop others from being notified Util.log(e); removeListener(listener); } } } /** * @return a collection of all the model extension definitions (never <code>null</code>) */ public Collection<ModelExtensionDefinition> getAllDefinitions() { return Collections.unmodifiableCollection(this.definitions.values()); } /** * @return a collection of the built-in model extension definitions (never <code>null</code>) */ public Collection<ModelExtensionDefinition> getBuiltInDefinitions() { Collection<ModelExtensionDefinition> builtInDefns = new ArrayList<ModelExtensionDefinition>(); for (ModelExtensionDefinition med : definitions.values()) { if (med.isBuiltIn()) { builtInDefns.add(med); } } return builtInDefns; } /** * @return a collection of the User-defined model extension definitions (never <code>null</code>) */ public Collection<ModelExtensionDefinition> getUserDefinedDefinitions() { Collection<ModelExtensionDefinition> userDefns = new ArrayList<ModelExtensionDefinition>(); for (ModelExtensionDefinition med : definitions.values()) { if (!med.isBuiltIn() && !med.isImported()) { userDefns.add(med); } } return userDefns; } /** * @return a collection of the imported model extension definitions (never <code>null</code>) */ public Collection<ModelExtensionDefinition> getImportedDefinitions() { Collection<ModelExtensionDefinition> importedDefns = new ArrayList<ModelExtensionDefinition>(); for (ModelExtensionDefinition med : definitions.values()) { if (med.isImported()) { importedDefns.add(med); } } return importedDefns; } /** * @param definedMedFiles * @param mxdType */ private IStatus addDefinitions(Collection<File> definedMedFiles, MxdType mxdType) { boolean allSuccess = true; for (File medFile : definedMedFiles) { try { ModelObjectExtensionAssistant assistant = ExtensionPlugin.getInstance() .createDefaultModelObjectExtensionAssistant(); ModelExtensionDefinition definition = addDefinition(new FileInputStream(medFile), assistant); if (MxdType.IMPORTED.equals(mxdType)) definition.markAsImported(); } catch (Exception e) { allSuccess = false; String errMessage = NLS.bind(Messages.errorAddingUserDefinition, medFile.getAbsolutePath()); Util.log(IStatus.ERROR, e, errMessage); } } if (allSuccess) return new Status(IStatus.OK, ExtensionConstants.PLUGIN_ID, 0, StringConstants.EMPTY_STRING, null); else return new Status(IStatus.WARNING, ExtensionConstants.PLUGIN_ID, 0, Messages.errorRestoringUserDefinitions, null); } /** * Restore the persisted user-defined MEDs from the file system (at the specified path location) into the registry. * * @param userDefinitionsPath the directory path where the MEDs are persisted (can be <code>null</code> ore empty if MEDs are * not being restored) * @return IStatus indicating successful loading. */ public IStatus restoreDefinedDefinitions( String userDefinitionsPath ) { MultiStatus status = new MultiStatus(ExtensionConstants.PLUGIN_ID, 0, StringConstants.EMPTY_STRING, null); ExtensionDefinitionsManager mgr = new ExtensionDefinitionsManager(userDefinitionsPath); Collection<File> definedMedFiles = mgr.retrieveDefinitionFiles(MxdType.USER); status.add(addDefinitions(definedMedFiles, MxdType.USER)); definedMedFiles = mgr.retrieveDefinitionFiles(MxdType.IMPORTED); status.add(addDefinitions(definedMedFiles, MxdType.IMPORTED)); return status; } /** * Save all user-defined and imported MEDs that are currently in the registry to the file system (at the specified path location). Any user * meds that have been previously saved at the specified location will be overwritten. * * @param definedDefinitionsPath the directory path where the MEDs are persisted (can be <code>null</code> ore empty if MEDs are * not being saved) */ public void saveDefinedDefinitions( String definedDefinitionsPath ) { ExtensionDefinitionsManager mgr = new ExtensionDefinitionsManager(definedDefinitionsPath); mgr.saveDefinitions(getUserDefinedDefinitions()); mgr.saveDefinitions(getImportedDefinitions()); } /** * @return a collection of all registered model extension definition namespace prefixes (never <code>null</code>) */ public Set<String> getAllNamespacePrefixes() { return new HashSet<String>(namespaces.values()); } /** * @return a collection of all registered model extension definition namespace URIs (never <code>null</code>) */ public Set<String> getAllNamespaceUris() { return this.namespaces.keySet(); } /** * @param namespacePrefix the namespace prefix whose model extension definition is being requested (cannot be <code>null</code> * or empty) * @return the model extension definition or <code>null</code> if not found */ public ModelExtensionDefinition getDefinition( String namespacePrefix ) { CoreArgCheck.isNotEmpty(namespacePrefix, "namespacePrefix is empty"); //$NON-NLS-1$ return this.definitions.get(namespacePrefix); } /** * @param namespaceUri the namespace URI whose model extension definition is being requested (cannot be <code>null</code> or * empty) * @return the model extension definition or <code>null</code> if not found */ public ModelExtensionDefinition getDefinitionWithNSUri( String namespaceUri ) { CoreArgCheck.isNotEmpty(namespaceUri, "namespacePrefix is empty"); //$NON-NLS-1$ ModelExtensionDefinition resultMed = null; Collection<ModelExtensionDefinition> allMeds = getAllDefinitions(); for (ModelExtensionDefinition med : allMeds) { if (namespaceUri.equals(med.getNamespaceUri())) { resultMed = med; break; } } return resultMed; } /** * @return a non-modifiable set of metamodel URIs that can have extension properties (never <code>null</code>) */ public Set<String> getExtendableMetamodelUris() { if (this.extendableMetamodelUris == null) { return Collections.emptySet(); } return this.extendableMetamodelUris; } /** * @param namespacePrefix the namespace prefix whose model extension assistant is being requested (cannot be <code>null</code>) * @return the model extension assistant (<code>null</code> if an assistant is not found) */ public ModelExtensionAssistant getModelExtensionAssistant( String namespacePrefix ) { ModelExtensionDefinition definition = getDefinition(namespacePrefix); if (definition != null) { return definition.getModelExtensionAssistant(); } return null; } /** * @param metaclassName the metaclass name whose model extension assistants are being requested * @return the model extension assistants of model extension definitions that extend the specified metaclass name (never * <code>null</code>) */ public Collection<ModelExtensionAssistant> getModelExtensionAssistants( String metaclassName ) { Collection<ModelExtensionAssistant> assistants = new ArrayList<ModelExtensionAssistant>(); for (ModelExtensionDefinition definition : getAllDefinitions()) { if (definition.extendsMetaclass(metaclassName)) { assistants.add(definition.getModelExtensionAssistant()); } } return assistants; } /** * @param metaclassName the metaclass name whose property definition is being requested (cannot be <code>null</code> or empty) * @param propId the property definition identifier (cannot be <code>null</code> or empty) * @return the requested property definition or <code>null</code> if not found */ public ModelExtensionPropertyDefinition getPropertyDefinition( String metaclassName, String propId ) { CoreArgCheck.isNotEmpty(propId, "propId is empty"); //$NON-NLS-1$ for (ModelExtensionDefinition definition : getAllDefinitions()) { ModelExtensionPropertyDefinition propDefn = definition.getPropertyDefinition(metaclassName, propId); if (propDefn != null) { return propDefn; } } // not found return null; } /** * @param namespacePrefix the namespace prefix being checked (cannot be <code>null</code> or empty) * @return <code>true</code> if there is a model extension definition with that namespace prefix registered */ public boolean isNamespacePrefixRegistered( String namespacePrefix ) { CoreArgCheck.isNotEmpty(namespacePrefix, "namespacePrefix is empty"); //$NON-NLS-1$ return this.definitions.containsKey(namespacePrefix); } /** * @param namespaceUri the namespace URI being checked (cannot be <code>null</code> or empty) * @return <code>true</code> if there is a model extension definition with that namespace URI registered */ public boolean isNamespaceUriRegistered( String namespaceUri ) { CoreArgCheck.isNotEmpty(namespaceUri, "namespaceUri is empty"); //$NON-NLS-1$ return this.namespaces.containsKey(namespaceUri); } /** * @param metamodelUri the metamodel URI being checked (cannot be <code>null</code> or empty) * @return <code>true</code> if the provided URI is included in the list of extendable metamodel URIs */ public boolean isExtendable( String metamodelUri ) { CoreArgCheck.isNotEmpty(metamodelUri, "metamodelUri is empty"); //$NON-NLS-1$ return this.extendableMetamodelUris.contains(metamodelUri); } /** * @param listener the listener being removed (cannot be <code>null</code>) * @return <code>true</code> if the listener was successfully removed */ public boolean removeListener( RegistryListener listener ) { CoreArgCheck.isNotNull(listener, "listener is null"); //$NON-NLS-1$ return this.listeners.remove(listener); } /** * <strong>This method should only be called at startup by the {@link org.teiid.designer.extension.ExtensionPlugin}.</strong> * * @param extendableMetamodelUris the metamodel URIs that can have extension properties (cannot be <code>null</code>) * @throws Exception if this method is called more than once */ public void setMetamodelUris( Set<String> extendableMetamodelUris ) throws Exception { CoreArgCheck.isNotNull(extendableMetamodelUris, "extendableMetamodelUris"); //$NON-NLS-1$ // this will be set at plugin startup and should not be set again assert (this.extendableMetamodelUris == null) : "Extendable metamodel URIs being set for second time"; //$NON-NLS-1$ this.extendableMetamodelUris = Collections.unmodifiableSet(extendableMetamodelUris); } }