/****************************************************************************** * Copyright (c) 2007 g-Eclipse consortium * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Initial development of the original code was made for * project g-Eclipse founded by European Union * project number: FP6-IST-034327 http://www.geclipse.eu/ * * Contributor(s): * UCY (http://www.ucy.cs.ac.cy) * - Nicholas Loulloudes (loulloudes.n@cs.ucy.ac.cy) * *****************************************************************************/ package eu.geclipse.batch.ui.editors; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.common.command.BasicCommandStack; import org.eclipse.emf.common.command.CommandStackListener; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl; import org.eclipse.emf.common.ui.MarkerHelper; import org.eclipse.emf.common.ui.editor.ProblemEditorPart; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; 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.util.EContentAdapter; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.edit.domain.IEditingDomainProvider; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory; import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory; import org.eclipse.emf.edit.ui.util.EditUIMarkerHelper; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.PartInitException; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.dialogs.SaveAsDialog; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.part.MultiPageEditorSite; import org.eclipse.wst.sse.ui.StructuredTextEditor; import eu.geclipse.batch.model.qdl.QueueType; import eu.geclipse.batch.model.qdl.util.QdlAdapterFactory; import eu.geclipse.batch.ui.internal.Activator; import eu.geclipse.batch.ui.internal.Messages; import eu.geclipse.batch.ui.internal.pages.AdvancedQueueConfigPage; import eu.geclipse.batch.ui.internal.pages.SimpleQueueConfigPage; /** * The QueueEditor class is responsible for instantiating a GUI multi-page editor for * editing files with the *.qdl extension. Such files have an XML content that describe the * configuration of Grid Batch Queues. * * The QueueEditor contains three pages: * 1) a page for manipulating simple Batch Queue configuration settings, * 2) a page for manipulating advanced Batch Queue configuration settings, * 3) a raw source editor for editing the raw XML content of the *.qdl file. * */ public class QueueEditor extends FormEditor implements IEditingDomainProvider { protected Map<Resource, Diagnostic> resourceToDiagnosticMap = new LinkedHashMap<Resource, Diagnostic>(); protected Collection<Resource> savedResources = new ArrayList<Resource>(); protected Collection<Resource> removedResources = new ArrayList<Resource>(); protected Collection<Resource> changedResources = new ArrayList<Resource>(); protected ISelection editorSelection = StructuredSelection.EMPTY; protected MarkerHelper markerHelper = new EditUIMarkerHelper(); protected AdapterFactoryEditingDomain editingDomain; protected boolean updateProblemIndication = true; protected ComposedAdapterFactory adapterFactory; protected QueueType queue = null; protected IResourceChangeListener resourceChangeListener = new IResourceChangeListener() { public void resourceChanged(final IResourceChangeEvent event) { // Only listening to these. // if (event.getType() == IResourceDelta.POST_CHANGE) { IResourceDelta delta = event.getDelta(); try { class ResourceDeltaVisitor implements IResourceDeltaVisitor { protected ResourceSet resourceSet = QueueEditor.this.editingDomain.getResourceSet(); protected Collection< Resource > changedRes= new ArrayList< Resource >(); protected Collection< Resource > removedRes = new ArrayList< Resource >(); public boolean visit( final IResourceDelta deltaIn ) { if ( deltaIn.getFlags() != IResourceDelta.MARKERS && deltaIn.getResource().getType() == IResource.FILE ) { if ( ( deltaIn.getKind() & ( IResourceDelta.CHANGED | IResourceDelta.REMOVED ) ) != 0 ) { Resource resource = this.resourceSet.getResource( URI.createURI( deltaIn.getFullPath().toString() ), false ); if ( resource != null ) { if ( ( deltaIn.getKind() & IResourceDelta.REMOVED) != 0 ) { this.removedRes.add( resource ); } else if ( !QueueEditor.this.savedResources.remove( resource ) ) { this.changedRes.add( resource ); } } } } return true; } public Collection< Resource > getChangedResources() { return this.changedRes; } public Collection< Resource > getRemovedResources() { return this.removedRes; } } ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(); delta.accept(visitor); if ( !visitor.getRemovedResources().isEmpty() ) { QueueEditor.this.removedResources.addAll( visitor.getRemovedResources() ); if ( !isDirty() ) { getSite().getShell().getDisplay().asyncExec ( new Runnable() { public void run() { getSite().getPage().closeEditor( QueueEditor.this, false ); } }); } } if ( !visitor.getChangedResources().isEmpty() ) { QueueEditor.this.changedResources.addAll( visitor.getChangedResources() ); if ( getSite().getPage().getActiveEditor() == QueueEditor.this ) { getSite().getShell().getDisplay().asyncExec ( new Runnable() { public void run() { handleActivate(); } }); } } } catch ( CoreException exception ) { Activator.logException( exception ); } } } }; protected EContentAdapter problemIndicationAdapter = new EContentAdapter() { @Override public void notifyChanged( final Notification notification ) { if ( notification.getNotifier() instanceof Resource ) { switch ( notification.getFeatureID( Resource.class ) ) { case Resource.RESOURCE__IS_LOADED: case Resource.RESOURCE__ERRORS: case Resource.RESOURCE__WARNINGS: { Resource resource = ( Resource )notification.getNotifier(); Diagnostic diagnostic = analyzeResourceProblems( ( Resource )notification.getNotifier(), null ); if ( diagnostic.getSeverity() != Diagnostic.OK ) { QueueEditor.this.resourceToDiagnosticMap.put( resource, diagnostic ); } else { QueueEditor.this.resourceToDiagnosticMap.remove( resource ); } if ( QueueEditor.this.updateProblemIndication ) { getSite().getShell().getDisplay().asyncExec ( new Runnable() { public void run() { updateProblemIndication(); } }); } } } } else { super.notifyChanged( notification ); } } @Override protected void setTarget(final Resource target) { basicSetTarget( target ); } @Override protected void unsetTarget( final Resource targetIn ) { basicUnsetTarget( targetIn ); } }; private StructuredTextEditor editor = null; private int sourcePageIndex; private boolean refreshedModel = false; private boolean isDirtyFlag = false; private SimpleQueueConfigPage simpleQueueConfigPage = new SimpleQueueConfigPage(this); private AdvancedQueueConfigPage advancedQueueConfigPage = new AdvancedQueueConfigPage(this); /** * QueueEditor Default Constructor */ public QueueEditor() { List<AdapterFactoryImpl> factories = new ArrayList<AdapterFactoryImpl>(); factories.add(new ResourceItemProviderAdapterFactory() ); factories.add(new QdlAdapterFactory()); factories.add(new ReflectiveItemProviderAdapterFactory()); this.adapterFactory = new ComposedAdapterFactory(factories); /* * Create the command stack that will notify this editor as commands * are executed. */ BasicCommandStack commandStack = new BasicCommandStack(); /* * Add a listener to set the most recent command's affected objects to be * the selection of the viewer with focus. * */ commandStack.addCommandStackListener (new CommandStackListener() { @SuppressWarnings("synthetic-access") public void commandStackChanged(final EventObject event) { Composite container2 = getContainer(); container2.getDisplay().asyncExec (new Runnable() { public void run() { firePropertyChange( IEditorPart.PROP_DIRTY ); } }); } }); /* * Create the editing domain with a special command stack. */ this.editingDomain = new AdapterFactoryEditingDomain(this.adapterFactory, commandStack, new HashMap<Resource, Boolean>()); } protected void cleanDirtyState() { this.simpleQueueConfigPage.setDirty( false ); this.advancedQueueConfigPage.setDirty( false ); } /** * This method set's the dirty status of the editor. * * @param dirtyFlag * If TRUE then the page is Dirty and a Save operation is needed. * */ public void setDirty( final boolean dirtyFlag ) { if ( this.isDirtyFlag != dirtyFlag ) { this.isDirtyFlag = dirtyFlag; this.editorDirtyStateChanged(); } } // End void setDirty() /* (non-Javadoc) * @see org.eclipse.ui.forms.editor.FormEditor#addPages() */ @Override protected void addPages() { getQdlModel(); try { addPage( this.simpleQueueConfigPage ); addPage( this.advancedQueueConfigPage ); addResourceEditorPage(); pushContentToPages(); } catch( PartInitException e ) { Activator.logException( e ); } } private void pushContentToPages() { this.simpleQueueConfigPage.setPageContent( this.queue, isModelRefreshed() ); this.advancedQueueConfigPage.setPageContent( this.queue, isModelRefreshed() ); } protected void refreshEditor() { this.refreshedModel = true; pushContentToPages(); this.refreshedModel = false; } protected void doTextEditorSave() { this.editor.doSave( null ); } /* (non-Javadoc) * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor) */ @Override public void doSave( final IProgressMonitor monitor ) { /* Do the work within an operation because this is a long running activity * that modifies the workbench. */ WorkspaceModifyOperation operation = new WorkspaceModifyOperation() { // This is the method that gets invoked when the operation runs. @Override public void execute( final IProgressMonitor monitorIn ) { // Save the resources to the file system. // boolean first = true; for ( Iterator<?> i = QueueEditor.this.editingDomain.getResourceSet().getResources().iterator(); i.hasNext(); ) { Resource resource = (Resource)i.next(); if ( ( first || !resource.getContents().isEmpty() || isPersisted(resource)) && !QueueEditor.this.editingDomain.isReadOnly(resource)) { try { QueueEditor.this.savedResources.add(resource); resource.save(Collections.EMPTY_MAP); } catch (Exception exception) { QueueEditor.this.resourceToDiagnosticMap.put(resource, analyzeResourceProblems(resource, exception)); //setDirty( false ); doTextEditorSave(); cleanDirtyState(); refreshEditor(); } first = false; } } } }; this.updateProblemIndication = false; try { // This runs the options, and shows progress. new ProgressMonitorDialog(getSite().getShell()).run(true, false, operation); // Refresh the necessary state. ((BasicCommandStack)this.editingDomain.getCommandStack()).saveIsDone(); //setDirty( false ); doTextEditorSave(); cleanDirtyState(); refreshEditor(); } catch (Exception exception) { // Something went wrong that shouldn't. Activator.logException( exception ); } this.updateProblemIndication = true; updateProblemIndication(); } @Override public void dispose() { this.updateProblemIndication = false; ResourcesPlugin.getWorkspace().removeResourceChangeListener( this.resourceChangeListener ); this.adapterFactory.dispose(); super.dispose(); if( this.queue != null ) { this.queue.eResource().unload(); this.queue = null; } } /* (non-Javadoc) * @see org.eclipse.ui.part.EditorPart#doSaveAs() */ @Override public void doSaveAs() { SaveAsDialog saveAsDialog= new SaveAsDialog(getSite().getShell()); saveAsDialog.open(); IPath path= saveAsDialog.getResult(); if ( path != null ) { IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); if ( file != null ) { doSaveAs(URI.createPlatformResourceURI(file.getFullPath().toString(), false), new FileEditorInput(file)); } } } /* Save the QDL file (used as the editor input) as a different name. */ protected void doSaveAs( final URI uri, final IEditorInput editorInput ) { this.editingDomain.getResourceSet().getResources().get( 0 ).setURI( uri ); setInputWithNotify( editorInput ); setPartName( editorInput.getName() ); IProgressMonitor progressMonitor = new NullProgressMonitor(); doSave( progressMonitor ); } /* (non-Javadoc) * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed() */ @Override public boolean isSaveAsAllowed() { return true; } public EditingDomain getEditingDomain() { return this.editingDomain; } /** * @return true if the the QDL Model was refreshed / changed. * This could be caused by an external editor. */ public boolean isModelRefreshed() { return this.refreshedModel; } /* * This method adds the XML Resource Editor Page to the Queue editor */ private void addResourceEditorPage()throws PartInitException{ this.sourcePageIndex = addPage( getSourceEditor(), getEditorInput() ); setPageText( this.sourcePageIndex, getEditorInput().getName() ); getSourceEditor().setInput( getEditorInput() ); } /* * This method returns a Text Editor for addResourceEditorPage method. */ private StructuredTextEditor getSourceEditor() { if ( this.editor == null ) { this.editor = new StructuredTextEditor(); this.editor.setEditorPart( this ); } return this.editor; } /** * @see org.eclipse.ui.part.MultiPageEditorPart#createSite(org.eclipse.ui.IEditorPart) */ @Override protected IEditorSite createSite( final IEditorPart page ) { IEditorSite site = null; if ( page == this.editor ) { site = new MultiPageEditorSite( this, page ) { @Override public String getId() { // Sets this ID so nested editor is configured for XML source return "org.eclipse.core.runtime.xml" + ".source"; //$NON-NLS-1$ //$NON-NLS-2$ } }; } else { site = super.createSite( page ); } return site; } @Override public void init( final IEditorSite site, final IEditorInput editorInput ) { setSite(site); setInputWithNotify( editorInput ); setPartName( editorInput.getName() ); ResourcesPlugin.getWorkspace() .addResourceChangeListener( this.resourceChangeListener, IResourceChangeEvent.POST_CHANGE ); } protected void handleActivate() { // Recompute the read only state. if ( this.editingDomain.getResourceToReadOnlyMap() != null ) { this.editingDomain.getResourceToReadOnlyMap().clear(); // Refresh any actions that may become enabled or disabled. setSelection( getSelection() ); } if ( !this.removedResources.isEmpty() ) { if ( handleDirtyConflict() ) { getSite().getPage().closeEditor( QueueEditor.this, false ); QueueEditor.this.dispose(); } else { this.removedResources.clear(); this.changedResources.clear(); this.savedResources.clear(); } } else if ( !this.changedResources.isEmpty() ) { this.changedResources.removeAll( this.savedResources ); handleChangedResources(); this.changedResources.clear(); this.savedResources.clear(); } } protected void handleChangedResources() { if ( !this.changedResources.isEmpty() && ( !isDirty() || handleDirtyConflict() ) ) { this.editingDomain.getCommandStack().flush(); this.updateProblemIndication = false; for ( Iterator< Resource > i = this.changedResources.iterator(); i.hasNext(); ) { Resource resource = i.next(); if ( resource.isLoaded() ) { resource.unload(); try { resource.load(Collections.EMPTY_MAP); } catch ( IOException exception ) { if (!this.resourceToDiagnosticMap.containsKey( resource ) ) { this.resourceToDiagnosticMap.put(resource, analyzeResourceProblems(resource, exception)); } } } } this.updateProblemIndication = true; updateProblemIndication(); getQdlModel(); } } protected boolean handleDirtyConflict() { return MessageDialog.openQuestion ( getSite().getShell(), "QueueEditor.FileConflict.label", //$NON-NLS-1$ "Queue.WARN.FileConflict" ); //$NON-NLS-1$ } /** * @return editorSelection */ public ISelection getSelection() { return this.editorSelection; } /** * @param selection */ public void setSelection( final ISelection selection ) { this.editorSelection = selection; } /* Method triggered when there are changes between the form pages.*/ @Override protected void pageChange( final int pageIndex ) { super.pageChange( pageIndex ); } /** * Responsible for de-serializing the model from the resource file. * The resource is passed to the getResourceRoot method. */ public void getQdlModel(){ // Assumes that the input is a file object. // IFileEditorInput modelFile = ( IFileEditorInput )getEditorInput(); URI resourceURI = URI.createPlatformResourceURI( modelFile.getFile().getFullPath().toString(), false ); Exception exception = null; Resource resource = null; try { // Load the resource through the editing domain. // resource = this.editingDomain.getResourceSet().getResource( resourceURI, true ); } catch ( Exception e ) { exception = e; resource = this.editingDomain.getResourceSet().getResource( resourceURI, false ); } Diagnostic diagnostic = analyzeResourceProblems( resource, exception ); if ( diagnostic.getSeverity() != Diagnostic.OK ) { this.resourceToDiagnosticMap.put( resource, analyzeResourceProblems( resource, exception ) ); } this.editingDomain.getResourceSet().eAdapters().add(this.problemIndicationAdapter); getResourceRoot( resource ); // This means the file was edited from an external editor so // push the new QDL model to the pages. if ( !this.changedResources.isEmpty() ){ refreshEditor(); } } /* This method parses the resource in order to find which QDL types appear. Each QDL type is then passed as a reference parameter (EList) in the appropriate page of the Queue editor. */ private void getResourceRoot( final Resource resource ) { // Get an iterator to iterate through all contents of the resource. TreeIterator <EObject> iterator = resource.getAllContents(); while ( iterator.hasNext ( ) ) { EObject testElement = iterator.next(); /* Instace-of checks for each EObject that appears in the resource. * We want to get the JobDefinition EObject which is the root Element of * a QDL Document. */ if ( testElement instanceof QueueType ) { this.queue = ( QueueType ) testElement; } } } /** * This looks up a string in plug-in.properties, making a substitution. * Returns a diagnostic describing the errors and warnings listed in * the resource and the specified exception * @param resource * @param exception * @return Diagnostic */ public Diagnostic analyzeResourceProblems( final Resource resource, final Exception exception ) { Diagnostic basicDiagnostic = null; if ( !resource.getErrors().isEmpty() || !resource.getWarnings().isEmpty() ) { basicDiagnostic = new BasicDiagnostic ( Diagnostic.ERROR, Activator.PLUGIN_ID, 0, Messages.getString( "QueueEditor.CreateModelErrorMessage" ), //$NON-NLS-1$ new Object [] { exception == null ? ( Object )resource : exception }); ( ( BasicDiagnostic ) basicDiagnostic ).merge( EcoreUtil.computeDiagnostic( resource, true ) ); } else if ( exception != null ) { basicDiagnostic = new BasicDiagnostic (Diagnostic.ERROR, Activator.PLUGIN_ID, 0, Messages.getString( "QueueEditor.CreateModelErrorMessage" ), //$NON-NLS-1$ new Object[] { exception }); } else { basicDiagnostic = Diagnostic.OK_INSTANCE; } return basicDiagnostic; } protected void updateProblemIndication() { if ( this.updateProblemIndication ) { BasicDiagnostic diagnostic = new BasicDiagnostic ( Diagnostic.OK, Activator.PLUGIN_ID, 0, null, new Object [] { this.editingDomain.getResourceSet() } ); for (Iterator<Diagnostic> i = this.resourceToDiagnosticMap.values().iterator(); i.hasNext(); ) { Diagnostic childDiagnostic = i.next(); if ( childDiagnostic.getSeverity() != Diagnostic.OK ) { diagnostic.add( childDiagnostic ); } } int lastEditorPage = getPageCount() - 1; if ( lastEditorPage >= 0 && getEditor( lastEditorPage ) instanceof ProblemEditorPart ) { ( ( ProblemEditorPart )getEditor( lastEditorPage ) ).setDiagnostic( diagnostic ); if ( diagnostic.getSeverity() != Diagnostic.OK ) { setActivePage(lastEditorPage); } } else if ( diagnostic.getSeverity() != Diagnostic.OK ) { ProblemEditorPart problemEditorPart = new ProblemEditorPart(); problemEditorPart.setDiagnostic(diagnostic); problemEditorPart.setMarkerHelper(this.markerHelper); try { addPage(++lastEditorPage, problemEditorPart, getEditorInput()); setPageText(lastEditorPage, problemEditorPart.getPartName()); setActivePage(lastEditorPage); } catch ( PartInitException exception ) { Activator.logException( exception ); } } if ( this.markerHelper.hasMarkers( this.editingDomain.getResourceSet() ) ) { this.markerHelper.deleteMarkers( this.editingDomain.getResourceSet() ); if ( diagnostic.getSeverity() != Diagnostic.OK ) { try { this.markerHelper.createMarkers(diagnostic); } catch ( CoreException exception ) { Activator.logException( exception ); } } } } } protected boolean isPersisted( final Resource resource ) { boolean result = false; try { InputStream stream = this.editingDomain.getResourceSet().getURIConverter().createInputStream(resource.getURI()); if ( stream != null ) { result = true; stream.close(); } } catch (IOException e) { Activator.logException( e ); } return result; } } // End QueueEditor Class