/* * 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.workspace; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.zip.Checksum; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xsd.util.XSDResourceImpl; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.ModelerCoreRuntimeException; import org.teiid.core.designer.util.ChecksumUtil; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.StreamPipe; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.XsdObjectExtension; 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.container.ResourceAction; import org.teiid.designer.core.resource.EmfResource; import org.teiid.designer.core.transaction.UnitOfWork; import org.teiid.designer.core.util.ModelContents; import org.teiid.designer.metamodels.core.ModelAnnotation; import org.teiid.designer.metamodels.core.extension.XPackage; /** * ModelBufferImpl * * @since 8.0 */ public class ModelBufferImpl implements ModelBuffer { private final IFile file; private final Openable owner; private final ResourceSet emfResourceSet; private Resource emfResource; private ModelContents contents; private boolean readonly; private Map options; private long lastModificationStamp; private long lastChecksum; private long lastFileSize; private String errorMessage; /** * Flag that is set to true just before the contents of the IFile are set, and set to false when the lastModificationStamp is * set immediately following. It is possible that events get processed after the contents are set but before the * lastModificationStamp is set; this flag is a hint to the resource processing the event to not unload. */ private boolean inProcessOfSavingContents; /** * Construct an instance of ModelBufferImpl. */ public ModelBufferImpl( final IFile file, final Openable owner, final ResourceSet emfResourceSet, final boolean readonly ) { this.file = file; this.owner = owner; if (file == null) { setReadOnly(readonly); } updateCachedFileInformation(); this.emfResourceSet = emfResourceSet; this.emfResource = null; this.options = new HashMap(); this.contents = null; this.inProcessOfSavingContents = false; this.errorMessage = null; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getEmfResource() */ @Override public Resource getEmfResource() { return this.emfResource; } protected ResourceSet getEmfResourceSet() { return this.emfResourceSet; } public ModelContents getModelContents() { if (this.contents == null && this.emfResource != null) { this.contents = new ModelContents(this.emfResource); } return this.contents; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getOwner() */ @Override public Openable getOwner() { return this.owner; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getUnderlyingResource() */ @Override public IResource getUnderlyingResource() { return this.file; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#hasUnsavedChanges() */ @Override public boolean hasUnsavedChanges() { if (this.emfResource != null) { return this.emfResource.isModified(); } return false; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#isClosed() */ @Override public boolean isClosed() { if (this.emfResource == null || !this.emfResource.isLoaded()) { return true; } return false; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#isReadOnly() */ @Override public boolean isReadOnly() { if (this.file == null) { return this.readonly; } return ModelUtil.isIResourceReadOnly(this.file); } /** * Sets this <code>Buffer</code> to be read only. */ protected void setReadOnly( final boolean readOnly ) { this.readonly = readOnly; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#unload() */ @Override public void unload() { if (!isClosed()) { this.emfResource.unload(); this.contents = null; updateCachedFileInformation(); this.errorMessage = null; } } protected void refresh( final IProgressMonitor progress ) { if (this.file != null) { try { // Refresh the file and update the lastModificationStamp ... this.file.refreshLocal(IResource.DEPTH_INFINITE, progress); updateCachedFileInformation(); this.errorMessage = null; } catch (CoreException err) { final Object[] params = new Object[] {this.file.getFullPath().toString()}; final String msg = ModelerCore.Util.getString("ModelBufferImpl.Error_while_refreshing", params); //$NON-NLS-1$ throw new ModelerCoreRuntimeException(err, msg); } } } protected void open( final IProgressMonitor progress ) { if (this.emfResourceSet != null && this.file != null) { IPath path = this.file.getLocation(); final URI uri = URI.createFileURI(path.toString()); if (ModelerCore.DEBUG_MODEL_WORKSPACE) { final String pathInProj = this.file.getProject().getName() + IPath.SEPARATOR + this.file.getProjectRelativePath(); final Object[] params = new Object[] {pathInProj}; ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("ModelBufferImpl.DEBUG.Opening_model", params)); //$NON-NLS-1$ } boolean startedTxn = false; UnitOfWork txn = null; try { if (emfResourceSet instanceof Container) { final Container cntr = ((Container)emfResourceSet); txn = cntr.getEmfTransactionProvider().getCurrent(); if (!txn.isStarted()) { try { txn.begin(); txn.setSignificant(false); txn.setUndoable(false); txn.setSource(cntr); // Defect 23120 - just adding a Description for this case so logging/debugging will know when this // class is the "source" of the txn txn.setDescription("ModelBufferImpl.open(" + this.getUnderlyingResource().getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (ModelerCoreException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } startedTxn = true; } } // Get or create the resource ... ResourceAction action = null; Resource duplicateOfResource = null; this.errorMessage = null; boolean justLoaded = false; try { action = ContainerImpl.getOrCreateResource(this.emfResourceSet, uri); // may throw DuplicateResourceException this.emfResource = action.getResource(); File file = this.file.getLocation().toFile(); if (file.canRead() && file.exists() && file.length() != 0 && !this.emfResource.isLoaded()) { this.emfResource.load(this.emfResourceSet.getLoadOptions()); justLoaded = true; } } catch (DuplicateResourceException e) { duplicateOfResource = e.getDuplicateOfModel(); this.errorMessage = e.getMessage(); } catch (Throwable t) { final Object[] params = new Object[] {uri}; final String msg = ModelerCore.Util.getString("ModelBufferImpl.Error_creating_resource_for_URI", params); //$NON-NLS-1$ throw new ModelerCoreRuntimeException(t, msg); } if (this.emfResource != null) { if (this.emfResource instanceof EmfResource) { this.contents = ((EmfResource)this.emfResource).getModelContents(); CoreArgCheck.isNotNull(this.contents); } if (this.contents == null) { this.contents = new ModelContents(this.emfResource); } // Register the resource final OpenableImpl modelResource = (OpenableImpl)this.getOwner(); modelResource.getBufferManager().registerEmfResource(this.emfResource, modelResource); if (justLoaded) { // Force creation of ModelAnnotation and save model this.getModelContents().getModelAnnotation(); try { // this.emfResource.save( new HashMap() ); this.updateCachedFileInformation(); } catch (Exception e) { final Object[] params = new Object[] {uri}; final String msg = ModelerCore.Util.getString("ModelBufferImpl.Unable_to_initialize_new_model_URI", params); //$NON-NLS-1$ throw new ModelerCoreRuntimeException(e, msg); } } if (this.file.exists()) { try { // Be sure to unmark as duplicate ... this.file.setSessionProperty(ModelerCore.DUPLICATE_MODEL_OF_IPATH_KEY, null); } catch (CoreException e) { ModelerCore.Util.log(e); } } } else if (duplicateOfResource != null) { final ModelResource duplicateOfModelResource = ModelerCore.getModelWorkspace().findModelResource(duplicateOfResource); if (duplicateOfModelResource == null) { throw new DuplicateResourceException(duplicateOfResource, null, this.errorMessage); } final IPath duplicateOfModelPath = duplicateOfModelResource.getPath(); // Mark the resource with a "duplicate" session property ... try { this.file.setSessionProperty(ModelerCore.DUPLICATE_MODEL_OF_IPATH_KEY, duplicateOfModelPath.toString()); } catch (CoreException err) { final Object[] params = new Object[] {uri, duplicateOfModelPath}; final String msg = ModelerCore.Util.getString("ModelBufferImpl.Unable_to_mark_resource_as_duplicate", params); //$NON-NLS-1$ throw new ModelerCoreRuntimeException(err, msg); } throw new DuplicateResourceException(duplicateOfResource, duplicateOfModelPath, this.errorMessage); } else { throw new ModelerCoreRuntimeException( ModelerCore.Util.getString("ModelBufferImpl.Could_not_resolve_local_resource_for_{0}_1", uri)); //$NON-NLS-1$ } } finally { if (startedTxn && txn != null) { try { txn.commit(); } catch (ModelerCoreException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } } // ensure resource does not have changes just because we opened it if (this.emfResource != null) { this.emfResource.setModified(false); } } } else { throw new ModelerCoreRuntimeException( ModelerCore.Util.getString("ModelBufferImpl.Can_not_open_Model_Buffer_with_both_a_null_ResourceSet_and_null_IFile_1")); //$NON-NLS-1$ } } protected void updateCachedFileInformation() { if (this.file == null) { this.lastModificationStamp = INITIAL_MOD_STAMP; this.lastFileSize = 0; this.lastChecksum = 0; } else { this.lastModificationStamp = this.file.getModificationStamp(); if (!this.file.exists()) { this.lastFileSize = 0; this.lastChecksum = 0; } else { InputStream stream = null; try { final IPath rawLocation = ModelUtil.getLocation(file); if (rawLocation != null) { final File rawFile = new File(rawLocation.toString()); if (rawFile.exists()) { this.lastFileSize = rawFile.length(); stream = new FileInputStream(rawFile); final InputStream buffer = new BufferedInputStream(stream); final Checksum checksum = ChecksumUtil.computeChecksum(buffer); this.lastChecksum = checksum.getValue(); } } } catch (Exception err) { ModelerCore.Util.log(err); } finally { if (stream != null) { try { stream.close(); } catch (IOException err1) { // Ignore; cant' do anything anyway } } } } } } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getErrors() * @since 4.2 */ @Override public IStatus getErrors() { if (hasErrors()) { final String msg = this.errorMessage != null ? this.errorMessage : ""; //$NON-NLS-1$ return new Status(IStatus.ERROR, ModelerCore.PLUGIN_ID, 0, msg, null); } return new Status(IStatus.OK, ModelerCore.PLUGIN_ID, 0, "", null); //$NON-NLS-1$ } /** * @see org.teiid.designer.core.workspace.ModelBuffer#hasErrors() * @since 4.2 */ @Override public boolean hasErrors() { if (this.file == null) { return false; } // See if the model was marked as a duplicate ... Object duplicateOfModel = null; try { duplicateOfModel = this.file.getSessionProperty(ModelerCore.DUPLICATE_MODEL_OF_IPATH_KEY); } catch (CoreException err) { // Do nothing; treat as tho not a duplicate ... } return duplicateOfModel != null; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#close() */ @Override public void close() { if (this.emfResource != null) { if ((this.file != null) && ModelerCore.DEBUG_MODEL_WORKSPACE) { final String pathInProj = this.file.getProject().getName() + IPath.SEPARATOR + this.file.getProjectRelativePath(); final Object[] params = new Object[] {pathInProj}; ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("ModelBufferImpl.DEBUG.Closing_model", params)); //$NON-NLS-1$ } // Remove the EMF resource from the resource set ... boolean startedTxn = false; UnitOfWork txn = null; try { // We assume any ModelWorkspace resources container will be the ModelContainer final Container cntr = ModelerCore.getModelContainer(); txn = cntr.getEmfTransactionProvider().getCurrent(); if (!txn.isStarted()) { try { txn.begin(); txn.setSignificant(false); txn.setUndoable(false); // Defect 23120 - just adding a Description for this case so logging/debugging will know when this // class is the "source" of the txn txn.setDescription("ModelBufferImpl.close(" + this.getUnderlyingResource().getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (ModelerCoreException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } startedTxn = true; } // Unregister the resource final OpenableImpl modelResource = (OpenableImpl)this.getOwner(); modelResource.getBufferManager().unregisterEmfResource(this.emfResource); this.emfResource.unload(); this.contents = null; } catch (CoreException theException) { ModelerCore.Util.log(IStatus.ERROR, theException, theException.getMessage()); } finally { if (startedTxn && txn != null) { try { txn.commit(); } catch (ModelerCoreException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } } } } updateCachedFileInformation(); } /** * @see org.teiid.designer.core.workspace.ModelBuffer#save(org.eclipse.core.runtime.IProgressMonitor) */ @Override public void save( final IProgressMonitor progress, boolean force ) throws ModelWorkspaceException { if (this.file == null) { return; } if (! ModelUtil.isLockedSourceObject(file) && ModelUtil.isIResourceReadOnly(file)) { final String pathInProj = this.file.getProject().getName() + IPath.SEPARATOR + this.file.getProjectRelativePath(); final Object[] params = new Object[] {pathInProj}; throw new ModelWorkspaceException(ModelerCore.Util.getString("ModelBufferImpl.Model_is_readonly", params)); //$NON-NLS-1$ } // use a platform operation to update the resource contents try { // Since the EMF Resource is saved using an OutputStream but the // Platform saves using an InputStream, use a stream pipe ... final StreamPipe pipe = new StreamPipe(); final InputStream istream = pipe.getInputStream(); final OutputStream ostream = pipe.getOutputStream(); // ------------------------------------------------------------------------- // Write to the pipe // ------------------------------------------------------------------------- if (ModelerCore.DEBUG_MODEL_WORKSPACE) { final String pathInProj = this.file.getProject().getName() + IPath.SEPARATOR + this.file.getProjectRelativePath(); final Object[] params = new Object[] {pathInProj}; ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("ModelBufferImpl.DEBUG.Saving_model", params)); //$NON-NLS-1$ } // Write the EMF Resource to the ostream (and in a separate thread) final Map theOptions = this.options; // must be in final variable new Thread("ModelBuffer.Save") { //$NON-NLS-1$ @Override public void run() { saveInTransaction(ostream, theOptions); } }.start(); // ------------------------------------------------------------------------- // Read from the pipe // ------------------------------------------------------------------------- // Put the contents into the file ... // // It is possible that events get processed // after the contents are set but before the lastModificationStamp is set; this flag is a hint // to the resource processing the event to not unload. if (this.file.exists()) { this.file.refreshLocal(IResource.DEPTH_ZERO, null); this.inProcessOfSavingContents = true; this.file.setContents(istream, force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, progress); } else { this.inProcessOfSavingContents = true; this.file.create(istream, force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, progress); } updateCachedFileInformation(); this.inProcessOfSavingContents = false; } catch (CoreException e) { throw new ModelWorkspaceException(e); } } void saveInTransaction( OutputStream ostream, Map theOptions ) { // Remove the EMF resource from the resource set ... boolean startedTxn = false; UnitOfWork txn = null; try { // We assume any ModelWorkspace resources container will be the ModelContainer final Container cntr = ModelerCore.getModelContainer(); txn = cntr.getEmfTransactionProvider().getCurrent(); if (!txn.isStarted()) { try { txn.begin(); txn.setSignificant(false); txn.setUndoable(false); // Defect 23120 - just adding a Description for this case so logging/debugging will know when this // class is the "source" of the txn txn.setDescription("ModelBufferImpl.saveInTransaction(" + this.getUnderlyingResource().getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (ModelerCoreException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } startedTxn = true; } try { final Resource resource = ModelBufferImpl.this.emfResource; // mmDefect_12555 - If the resource is an XSD resource then make sure any extension package // value is set on the schema annotation if (resource instanceof XSDResourceImpl) { ModelAnnotation annotation = ModelBufferImpl.this.contents.getModelAnnotation(); XPackage extPackage = annotation.getExtensionPackage(); try { if (extPackage != null) { XsdObjectExtension.setExtensionPackage((XSDResourceImpl)resource, extPackage); } else { XsdObjectExtension.removeExtensionPackage((XSDResourceImpl)resource); } } catch (ModelerCoreException err) { ModelerCore.Util.log(IStatus.ERROR, err.getMessage()); } } ModelBufferImpl.this.emfResource.save(ostream, theOptions); } catch (IOException e) { // Do nothing, since this was probably a cancel from // the file.setContents(...) } finally { try { ostream.close(); } catch (IOException e1) { final String pathInProj = file.getProject().getName() + IPath.SEPARATOR + file.getProjectRelativePath(); final Object[] params = new Object[] {pathInProj, e1.getMessage()}; ModelerCore.Util.log(IStatus.INFO, e1, ModelerCore.Util.getString("ModelBufferImpl.Error_closing_stream", params)); //$NON-NLS-1$ } } } catch (CoreException theException) { ModelerCore.Util.log(IStatus.ERROR, theException, theException.getMessage()); } finally { if (startedTxn && txn != null) { try { txn.commit(); } catch (ModelerCoreException e) { ModelerCore.Util.log(IStatus.ERROR, e, e.getMessage()); } } } } /** * @return */ public Map getOptions() { return options; } /** * @param map */ public void setOptions( Map map ) { options = map; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getLastModificationStamp() * @since 4.2 */ @Override public long getLastModificationStamp() { return lastModificationStamp; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getLastFileSize() * @since 4.2 */ @Override public long getLastFileSize() { return lastFileSize; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#getLastChecksum() * @since 4.2 */ @Override public long getLastChecksum() { return lastChecksum; } /** * @see org.teiid.designer.core.workspace.ModelBuffer#isInProcessOfSaving() * @since 4.2 */ @Override public boolean isInProcessOfSaving() { return inProcessOfSavingContents; } }