/* * 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.core.resource; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.WrappedException; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.edit.domain.IEditingDomainProvider; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.id.IDGenerator; import org.teiid.core.designer.id.InvalidIDException; import org.teiid.core.designer.id.ObjectID; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.StringConstants; import org.teiid.designer.common.xmi.XMIHeader; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.container.Container; import org.teiid.designer.core.container.ContainerImpl; import org.teiid.designer.core.container.DuplicateResourceException; import org.teiid.designer.core.metamodel.MetamodelRegistry; import org.teiid.designer.core.resource.xmi.MtkXmiResourceFactory; import org.teiid.designer.core.transaction.UnitOfWork; import org.teiid.designer.core.types.DatatypeConstants; import org.teiid.designer.core.types.DatatypeManager; import org.teiid.designer.core.util.BasicUriPathConverter; import org.teiid.designer.core.util.UriPathConverter; import org.teiid.designer.core.workspace.ModelFileUtil; /** * @since 8.0 */ public class EmfResourceSetImpl extends ResourceSetImpl implements EmfResourceSet, IEditingDomainProvider { private final Container container; private ResourceSet[] externalResourceSets; private UriPathConverter pathConverter; /** * Constructor for EmfResourceSetImpl. * * @param container The {@link Container} referencing this */ public EmfResourceSetImpl( Container container ) { super(); if (container == null) { final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.The_Container_reference_may_not_be_null_1"); //$NON-NLS-1$ throw new IllegalArgumentException(msg); } this.container = container; this.externalResourceSets = new ResourceSet[0]; } /** * @see org.eclipse.emf.ecore.resource.impl.ResourceSetImpl#createResource(org.eclipse.emf.common.util.URI) * @since 4.2 */ @Override public Resource createResource( URI uri ) { canCreateResource(uri); return super.createResource(uri); } /** * Determine whether the resource given by the URI can be loaded. This method should throw a {@link WrappedException} or a * runtime exception if the resource cannot be loaded. * * @param uri * @return * @since 4.2 */ protected void canCreateResource( final URI uri ) { final XMIHeader header = doGetXMIHeader(uri); if (header != null) { // There is already a file at this URI, so check the UUID ... final String uuidString = header.getUUID(); if (uuidString != null) { try { final ObjectID uuid = IDGenerator.getInstance().stringToObject(uuidString); Resource myExistingResource = this.container.getResourceFinder().findByUUID(uuid, false); if (myExistingResource != null) { final URI existingResourceUri = myExistingResource.getURI(); final Object[] params = new Object[] {URI.decode(uri.toString()), URI.decode(existingResourceUri.toString())}; final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.Unable_to_load_model_at_0_because_same_as_1", params); //$NON-NLS-1$ throw new DuplicateResourceException(myExistingResource, null, msg); } final Object existingObject = this.container.getEObjectFinder().find(uuid); if (existingObject != null) { // There is already an object in the container with this ID; don't allow the loading ... if (existingObject instanceof EObject) { // First, find the resource URI for the object that already exists ... final Resource existingResource = ((EObject)existingObject).eResource(); if (existingResource != null) { // If the resource associated with the existing object does not exist // in this resource set then the existing object must have been found // in some external resource set and is not considered a duplicate // Fix for 18439 if (existingResource instanceof EmfResource) { Container cntr = ((EmfResource)existingResource).getContainer(); if (cntr != this.getContainer()) { return; } } final ResourceSet set = existingResource.getResourceSet(); if (set instanceof EmfResourceSet && set != this) { return; } final URI existingResourceUri = existingResource.getURI(); final Object[] params = new Object[] {URI.decode(uri.toString()), URI.decode(existingResourceUri.toString())}; final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.Unable_to_load_model_at_0_because_same_as_1", params); //$NON-NLS-1$ throw new DuplicateResourceException(existingResource, null, msg); } } // Use a default message if necessary ... final Object[] params = new Object[] {uri}; final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.Unable_to_load_model_at_0_because_already_loaded", params); //$NON-NLS-1$ throw new DuplicateResourceException(msg); } } catch (InvalidIDException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } } } } /** * Returns a resolved resource available outside of the resource set. It is called by {@link #getResource(URI, boolean) * getResource(URI, boolean)} after it has determined that the URI cannot be resolved based on the existing contents of the * resource set. * * @param uri the URI * @param loadOnDemand whether demand loading is required. */ @Override protected Resource delegatedGetResource( URI uri, boolean loadOnDemand ) { if (ModelerCore.DEBUG && ModelerCore.DEBUG_METAMODEL) { Object[] params = new Object[] {uri, new Boolean(loadOnDemand)}; ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("EmfResourceSetImpl.DEBUG.EmfResourceSetImpl.delegatedGetResource_for_URI,_loadOnDemand_2", params)); //$NON-NLS-1$ } // Check the metamodel registry for this URI final MetamodelRegistry registry = this.getContainer().getMetamodelRegistry(); if (registry != null && registry.containsURI(uri)) { final Resource resource = registry.getResource(uri); if (ModelerCore.DEBUG && ModelerCore.DEBUG_METAMODEL) { Object[] params = new Object[] {uri}; ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("EmfResourceSetImpl.DEBUG.Returning_resource_in_the_MetamodelRegistry_for_URI_3", params)); //$NON-NLS-1$ } return resource; } // Check all read-only resource sets for this URI for (int i = 0; i < this.externalResourceSets.length; i++) { ResourceSet resourceSet = this.externalResourceSets[i]; Resource resource = resourceSet.getResource(uri, false); if (resource != null) { return resource; } } return super.delegatedGetResource(uri, loadOnDemand); } /** * @see org.eclipse.emf.ecore.resource.ResourceSet#getResource(org.eclipse.emf.common.util.URI, boolean) */ @Override public Resource getResource( final URI uri, final boolean loadOnDemand ) { Resource resource = null; URI resourceURI = uri; try { // Get current uow UnitOfWork txn = container.getEmfTransactionProvider().getCurrent(); boolean alreadyStarted = true; if (!txn.isStarted()) { txn.begin(); alreadyStarted = false; } // First try retrieving the resource without forcing a load resource = super.getResource(resourceURI, false); // If the resource was not found, next check if the resource URI string // is a workspace relative uri of the form "/Project/.../Resource" then // convert it into absolute file URI and try retrieving the resource again. boolean canSearchWorkspace = canSearchWorkspace(); if (resource == null && resourceURI.isRelative() && canSearchWorkspace) { resourceURI = this.getUriPathConverter().makeAbsolute(resourceURI, null); resource = super.getResource(resourceURI, false); } // If the resource was still not found, check if the resource URI has a // file extension that is currently unknown to the resource factory registry if (resource == null) { // If this URI represents a Teiid Designer model file for which // there is no set Resource.Factory registered, then register // one before loading it. Fix for defect 11168. this.registerResourceExtension(resourceURI, loadOnDemand); } // If the resource was not found or not yet loaded ... if (resource == null || (!resource.isLoaded() && loadOnDemand)) { if (resourceURI.isFile()) { File resourceFile = new File(resourceURI.toFileString()); if (resourceFile.exists()) { resource = super.getResource(resourceURI, loadOnDemand); } } else { resource = super.getResource(resourceURI, loadOnDemand); } // It must have just be loaded so it should not be considered modified if (resource != null) { resource.setModified(false); } } if (!alreadyStarted) { txn.commit(); } } catch (ModelerCoreException t) { final Object[] params = new Object[] {uri, new Boolean(loadOnDemand)}; final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.Error_in_EmfResourceSetImpl.getResource()_2", params); //$NON-NLS-1$ ModelerCore.Util.log(IStatus.ERROR, t, msg); // don't throw } catch (DuplicateResourceException t) { throw t; // throw things from super.getResource(...) } catch (RuntimeException t) { final Object[] params = new Object[] {uri, new Boolean(loadOnDemand)}; final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.Error_in_EmfResourceSetImpl.getResource()_2", params); //$NON-NLS-1$ ModelerCore.Util.log(IStatus.ERROR, t, msg); throw t; // throw things from super.getResource(...) } return resource; } /** * @see org.eclipse.emf.ecore.resource.ResourceSet#getEObject(org.eclipse.emf.common.util.URI, boolean) */ @Override public EObject getEObject( URI uri, boolean loadOnDemand ) { // super.getEObject(uri, loadOnDemand); CoreArgCheck.isNotNull(uri); if (uri != null) { URI resourceURI = uri.trimFragment(); String resourceUriString = resourceURI.toString(); String uriFragmentString = uri.fragment(); // If the resource is the built-in datatypes resource then we use // the datatype manager to lookup the EObject. It is better to // use the datatype manager which will find a datatype by UUID string // or name whereas calling resource.getEObject(String) only works // with one of those forms. The logic to use the datatype manager // is also necessary to allow older 4.0 models to be read into a 4.1 // or later modeler instance. if (DatatypeConstants.BUILTIN_DATATYPES_URI.equals(resourceUriString) || ModelerCore.XML_SCHEMA_GENERAL_URI.equals(resourceUriString)) { try { DatatypeManager dtMgr = container.getDatatypeManager(); if (dtMgr != null) { return dtMgr.findDatatype(uriFragmentString); } final Object[] params = new Object[] {uri, container}; final String msg = ModelerCore.Util.getString("EmfResourceSetImpl.Error_in_EmfResourceSetImpl.getResource()_3", params); //$NON-NLS-1$ ModelerCore.Util.log(IStatus.ERROR, msg); } catch (ModelerCoreException e1) { ModelerCore.Util.log(IStatus.ERROR, e1, e1.getMessage()); } } // Call EmfResourceSet.getResource(URI,false) using the URI without its // fragment. If the result is non-null, then the Resource exists in the // ResourceSet, so try to resolve the fragment portion of the URI. Return // either null if the fragment doesn't resolve, or the EObject from the // resolution of the fragment. Resource resource = this.getResource(resourceURI, false); if (resource != null) { resource = this.getResource(resourceURI, loadOnDemand); return super.getEObject(uri, loadOnDemand); } // If the resource URI string is a workspace relative uri of the // form "/Project/.../Resource" then convert it into absolute file // URI and try the lookup again. boolean canSearchWorkspace = canSearchWorkspace(); if (resourceURI.isRelative() && canSearchWorkspace) { resourceURI = this.getUriPathConverter().makeAbsolute(resourceURI, null); resource = super.getResource(resourceURI, false); if (resource != null) { return resource.getEObject(uriFragmentString); } } // If the resource URI is a file URI but there is no file on the file system // corresponding to this location then return null because there is no resource // that can be loaded to resolve this proxy and we do not want to create an // empty resource in our container by calling getResource(URI,true) if (resourceURI.isFile()) { File resourceFile = new File(resourceURI.toFileString()); if (!resourceFile.exists()) { return null; } } // The result from EmfResourceSet.getResource(URI,false) returned null, so // either the underlying resource is available but has not yet been needed (and // no Resource exists for it), or there isn't even an underlying resource // (i.e., the URI is bad). In either case, call ResourceSet.getResource(URI,loadOnDemand) // within a try-catch (EmfResourceSet.getResource(URI,loadOnDemand) is not called because // it catches and logs all runtime exceptions). If there is no exception, continue with // the resolution of the fragment and return either the resolved EObject or null. // If there is an exception, then there is no underlying resource, so return null // and do not attempt to resolve any fragment (which may be a UUID) against any other // resource within the workspace. Resetting of eProxy URIs will occur when the // model imports for this resource are reorganized. try { resource = super.getResource(resourceURI, loadOnDemand); if (resource != null) { return resource.getEObject(uriFragmentString); } } catch (Throwable e) { // There is no underlying resource so return null resource = this.getResource(resourceURI, false); if (resource != null) { this.getResources().remove(resource); } return null; } } return super.getEObject(uri, loadOnDemand); } /** * @see org.teiid.designer.core.resource.EmfResourceSet#getContainer() */ @Override public Container getContainer() { return this.container; } /** * @see org.eclipse.emf.edit.domain.IEditingDomainProvider#getEditingDomain() */ @Override public EditingDomain getEditingDomain() { return ((ContainerImpl)container).getEditingDomain(); } /** * Add a ResourceSet to be used for resolution of a resource URI. The specified ResourceSet will be treated as read-only and * will never be used to load a resource for the URI being checked. * * @param listener */ public void addExternalResourceSet( final ResourceSet resourceSet ) { if (resourceSet != null) { ArrayList tmp = new ArrayList(); tmp.addAll(Arrays.asList(this.externalResourceSets)); tmp.add(resourceSet); this.externalResourceSets = new ResourceSet[tmp.size()]; tmp.toArray(this.externalResourceSets); } } /** * Return the array of external resource sets registered with this resource set. * * @return * @since 4.3 */ public ResourceSet[] getExternalResourceSets() { return this.externalResourceSets; } /** * <p> * </p> * * @see org.eclipse.emf.ecore.resource.impl.ResourceSetImpl#getLoadOptions() * @since 4.0 */ @Override public Map getLoadOptions() { final Map options = new HashMap(super.getLoadOptions()); final Map cntrOptions = this.container.getOptions(); if (cntrOptions.containsKey(XMLResource.OPTION_XML_MAP)) { options.put(XMLResource.OPTION_XML_MAP, cntrOptions.get(XMLResource.OPTION_XML_MAP)); } // options.put(XMLResource.OPTION_DISABLE_NOTIFY, Boolean.TRUE); return options; } /** * @return */ public UriPathConverter getUriPathConverter() { if (this.pathConverter == null) { this.pathConverter = new BasicUriPathConverter(); } return pathConverter; } /** * @param helper */ public void setUriPathConverter( final UriPathConverter converter ) { this.pathConverter = converter; } /** * Register a Resource.Factory for URI's file extension if this URI represents a Teiid Designer model file for which there is * no set factory. */ protected void registerResourceExtension( final URI uri, final boolean loadOnDemand ) { // If we are demanding the load of a resource for which we do not // have a Resource.Factory already registered, then check if (uri != null && loadOnDemand) { final String extension = uri.fileExtension(); // If the URI is to a VDB archive file then return. We cannot register // this extension to be created as a MtkXmiResourceFactory // if (uri != null && ModelUtil.EXTENSION_VDB.equalsIgnoreCase(extension)) { // return; // } // Get the registry map from file extension to resource factory ... final Resource.Factory.Registry registry = super.getResourceFactoryRegistry(); final Map extensionToFactoryMap = registry.getExtensionToFactoryMap(); // If a Resource.Factory already is already registered for this extension, then return if (extensionToFactoryMap.get(extension) != null) { return; } // If the URI represents the path to an existing file ... XMIHeader header = doGetXMIHeader(uri); // If the header is not null then we know the file is, at least, // a well formed xml document. if (header != null) { // If the XMI version for the header is not null, then return // if the file represents an older 1.X model file if (header.getXmiVersion() != null && header.getXmiVersion().startsWith("1.")) { //$NON-NLS-1$ return; } // If the UUID for the header is not null, then the file is a // Teiid Designer model file containing a ModelAnnotation element // Register the same Resource.Factory used with .xmi model files // for this extension ... if (header.getUUID() != null) { Resource.Factory factory = (Resource.Factory)extensionToFactoryMap.get(StringConstants.XMI); if (factory != null) { extensionToFactoryMap.put(extension, factory); } else { extensionToFactoryMap.put(extension, new MtkXmiResourceFactory()); } } } } } protected XMIHeader doGetXMIHeader( final URI uri ) { // First, normalize the URI to the 'physical' location ... URIConverter theURIConverter = getURIConverter(); URI normalizedURI = theURIConverter.normalize(uri); // If the file has an absolute path ... // [note: test isFile first, because URI.toFileString returns NULL if not a file.] if (normalizedURI.isFile() && normalizedURI.hasAbsolutePath()) { // Find the corresponding file for this location ... File resource = new File(normalizedURI.toFileString()); // Return the header only if the file exists (if it doesn't exist, there's nothing to read) ... if (resource.exists()) { XMIHeader header = ModelFileUtil.getXmiHeader(resource); return header; } } return null; } protected boolean canSearchWorkspace() { return ModelerCore.isModelContainer(getContainer()); // (ModelerCore.getWorkspace() != null); } }