| /******************************************************************************* |
| * Copyright (c) 2010 IBM Corporation and others. |
| * 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 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.jsp.core.internal.java; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InvalidClassException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.zip.CRC32; |
| |
| 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.Platform; |
| import org.eclipse.core.runtime.content.IContentType; |
| import org.eclipse.jst.jsp.core.internal.JSPCorePlugin; |
| import org.eclipse.jst.jsp.core.internal.Logger; |
| import org.eclipse.jst.jsp.core.internal.modelhandler.ModelHandlerForJSP; |
| import org.eclipse.jst.jsp.core.internal.provisional.contenttype.ContentTypeIdForJSP; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.FileBufferModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| |
| /** |
| * <p>This {@link IResourceChangeListener} is used to keep the {@link JSPTranslator}s for JSP |
| * resources persisted to disk. It can also be used to get persisted translators</p> |
| * <p>This class should be registered as an {@link IResourceChangeListener} on the Workspace |
| * as well as processing resource change events from a saved state, for example use see below.<p> |
| * <p><b>Plugin Activation:</b> |
| * <pre> |
| * try { |
| * ISavedState savedState = ResourcesPlugin.getWorkspace().addSaveParticipant( |
| * plugin.getBundle().getSymbolicName(), this.fSaveParticipant); |
| * if (savedState != null) { |
| * savedState.processResourceChangeEvents(JSPTranslatorPersistor.getDefault()); |
| * } |
| * } catch(CoreException e) {} |
| * ResourcesPlugin.getWorkspace().addResourceChangeListener(JSPTranslatorPersistor.getDefault()); |
| * </pre> |
| * <b>Plugin Deactivation:</b> |
| * <pre> |
| * ResourcesPlugin.getWorkspace().removeSaveParticipant(plugin.getBundle().getSymbolicName()); |
| * ResourcesPlugin.getWorkspace().removeResourceChangeListener(JSPTranslatorPersistor.getDefault()); |
| * </pre></p> |
| * |
| * <p>This class can be deactivated through the <code>persistJSPTranslations</code> system property, |
| * a value of <code>true</code> means the persister is activated (which is the default), value of |
| * <code>false</code> means the persister is not activated.</p> |
| */ |
| public class JSPTranslatorPersister implements IResourceChangeListener { |
| /** |
| * <code>true</code> if the persister is activated, <code>false</code> |
| * otherwise. This is determined by checking the system property |
| * <code>persistJSPTranslations</code>, if no value supplied then |
| * default is <code>true</code> |
| */ |
| public static final boolean ACTIVATED = |
| Boolean.valueOf(System.getProperty("persistJSPTranslations", "true")).booleanValue(); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| /** the location where {@link JSPTranslator}s are externalized too for persistence purposes */ |
| private static final IPath PERSIST_LOCATION = JSPCorePlugin.getDefault().getStateLocation().append("translators"); //$NON-NLS-1$ |
| |
| /** used to calculate persisted translator file names */ |
| private static final CRC32 CHECKSUM_CALC = new CRC32(); |
| |
| /** singleton instance of the {@link JSPTranslatorPersister} */ |
| private static final JSPTranslatorPersister INSTANCE = new JSPTranslatorPersister(); |
| |
| /** |
| * Used to handle resource change events |
| * @see #resourceChanged(IResourceChangeEvent) |
| */ |
| private IResourceDeltaVisitor fResourceDeltaVisitor; |
| |
| /** |
| * <p>Private singleton default constructor</p> |
| */ |
| private JSPTranslatorPersister() { |
| this.fResourceDeltaVisitor = new JSPResourceVisitor(); |
| } |
| |
| /** |
| * <p><b>NOTE: </b><i>This can possible return <code>null</code></i></p> |
| * |
| * @return Singleton instance of the {@link JSPTranslatorPersister} if |
| * {@link #ACTIVATED} is <code>true</code>, <code>null</code> otherwise. |
| */ |
| public static JSPTranslatorPersister getDefault() { |
| return ACTIVATED ? INSTANCE : null; |
| } |
| |
| /** |
| * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) |
| */ |
| public void resourceChanged(IResourceChangeEvent event) { |
| // only analyze the full (starting at root) delta hierarchy |
| IResourceDelta delta = event.getDelta(); |
| if (delta != null && delta.getFullPath().toString().equals("/")) { //$NON-NLS-1$ |
| try { |
| //use visitor to visit all children |
| delta.accept(this.fResourceDeltaVisitor, false); |
| } catch (CoreException e) { |
| Logger.logException("Processing resource change event delta failed, " + |
| "persisted JSPTranslators may not have been updated.", e); |
| } |
| } |
| } |
| |
| /** |
| * <p>Given the {@link IStructuredModel} of a JSP file attempts to retrieve the persisted |
| * {@link JSPTranslator} for that model.</p> |
| * <p><b>NOTE: </b><i>It is possible for there not to be a persisted translator</i></p> |
| * |
| * @param model {@link IStructuredModel} to get the persisted {@link JSPTranslator} for |
| * @return the persisted {@link JSPTranslator} for the given <code>model</code>, or |
| * <code>null</code> if none could be found or an existing one could not be read |
| */ |
| public static JSPTranslator getPersistedTranslator(IStructuredModel model) { |
| String persistedTranslatorFilePath = getPersistedTranslatorFilePath(model.getBaseLocation()); |
| File persistedTranslatorFile = new File(persistedTranslatorFilePath); |
| |
| //attempt to read in the externalized translator |
| JSPTranslator translator = null; |
| ObjectInputStream in = null; |
| try { |
| //get the persisted translator file if one exists |
| if(persistedTranslatorFile.exists()) { |
| long persistedTranslatorFileTimestamp = persistedTranslatorFile.lastModified(); |
| long jspFileTimestamp = FileBufferModelManager.getInstance().getBuffer( |
| model.getStructuredDocument()).getModificationStamp(); |
| |
| /* if the persisted translator timestamp is newer then the jsp file timestamp |
| * then the translation has not become stale, otherwise it has so delete |
| * it and don't use it */ |
| if(persistedTranslatorFileTimestamp > jspFileTimestamp) { |
| FileInputStream fis = new FileInputStream(persistedTranslatorFile); |
| in = new ObjectInputStream(fis); |
| translator = (JSPTranslator)in.readObject(); |
| |
| //set up some fields that were not externalized but can be retrieved from model |
| if(translator != null) { |
| translator.fStructuredDocument = model.getStructuredDocument(); |
| if(model instanceof IDOMModel) { |
| translator.fStructuredModel = (IDOMModel)model; |
| } |
| } |
| } else { |
| persistedTranslatorFile.delete(); |
| } |
| } |
| } catch(InvalidClassException e) { |
| /* this means that the externalized translator is for an older version |
| * of the JSPTranslator, so delete it */ |
| persistedTranslatorFile.delete(); |
| }catch (IOException e) { |
| Logger.logException("Could not read externalized JSPTranslator at " + persistedTranslatorFilePath, e); |
| } catch (ClassNotFoundException e) { |
| Logger.logException("Class of a serialized JSPTranslator cannot be found", e); |
| } finally { |
| if(in != null) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| Logger.logException("Could not close externalized JSPTranslator that was just read", e); |
| } |
| } |
| } |
| |
| return translator; |
| } |
| |
| /** |
| * <p>Given the path to a JSP file determines the path to its persisted {@link JSPTranslator}</p> |
| * |
| * @param jspFilePath {@link IPath} to JSP file for which the path to its persisted {@link JSPTranslator} |
| * should be determined |
| * |
| * @return OS file path to the persisted {@link JSPTranslator} associated with the JSP file at |
| * <code>jspFilePath</code> |
| */ |
| protected static String getPersistedTranslatorFilePath(String jspFilePath) { |
| CHECKSUM_CALC.reset(); |
| CHECKSUM_CALC.update(jspFilePath.getBytes()); |
| String persistedTranslatorFileName = Long.toString(CHECKSUM_CALC.getValue()) + ".translator"; //$NON-NLS-1$ |
| IPath location = PERSIST_LOCATION; |
| |
| // ensure the folder exists on disk |
| File folder = new File(location.toOSString()); |
| if (!folder.isDirectory()) { |
| try { |
| folder.mkdir(); |
| } |
| catch (SecurityException e) { |
| } |
| } |
| |
| location = location.addTrailingSeparator(); |
| location = location.append(persistedTranslatorFileName); |
| return location.toOSString(); |
| } |
| |
| /** |
| * @see JSPResourceVisitor#visit(IResourceDelta) |
| */ |
| private class JSPResourceVisitor implements IResourceDeltaVisitor { |
| /** |
| * <p>Default constructor</p> |
| */ |
| protected JSPResourceVisitor() { |
| } |
| |
| /** |
| * <p>For each {@link IResourceDelta} determine if its a JSP resource and if it is |
| * update its persisted translator accordingly</p> |
| * |
| * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta) |
| */ |
| public boolean visit(IResourceDelta delta) throws CoreException { |
| if(isJSPResource(delta.getResource())) { |
| switch (delta.getKind()) { |
| case IResourceDelta.CHANGED : |
| case IResourceDelta.ADDED : { |
| /* if a move, then move the persisted translation |
| * else create a new persisted translation, if its a change then |
| * the old persisted translation will be overwritten */ |
| if((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { |
| renamePersistedTranslator(delta.getMovedFromPath(), delta.getFullPath()); |
| } else { |
| IPath jspFilePath = delta.getFullPath(); |
| JSPTranslator translator = getJSPTranslator(jspFilePath); |
| if(translator != null) { |
| persistTranslator(translator, jspFilePath); |
| } |
| } |
| |
| break; |
| } |
| case IResourceDelta.REMOVED : { |
| /* only remove if its not a move, |
| * if it is a move the added file delta event will move translation */ |
| if((delta.getFlags() & IResourceDelta.MOVED_TO) == 0) { |
| deletePersistedTranslator(delta.getFullPath()); |
| } |
| break; |
| } |
| } |
| } |
| |
| //visit children deltas |
| return true; |
| } |
| |
| /** |
| * <p>Determines if an {@link IResource} is a JSP resource</p> |
| * |
| * @param resource determine if this {@link IResource} is a JSP resource |
| * @return <code>true</code> if <code>resource</code> is a JSP resource, |
| * <code>false</code> otherwise. |
| */ |
| private boolean isJSPResource(IResource resource) { |
| boolean isJSP = false; |
| |
| //general rule for getting files in the workspace |
| if(resource.getFullPath().segmentCount() >= 2) { |
| IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(resource.getFullPath()); |
| if(file.getType() == IResource.FILE) { |
| //get JSP content type each time because there is a possibility it could change |
| IContentType contentTypeJSP = Platform.getContentTypeManager().getContentType( |
| ContentTypeIdForJSP.ContentTypeID_JSP); |
| |
| isJSP = contentTypeJSP.isAssociatedWith(file.getName()); |
| } |
| } |
| |
| return isJSP; |
| } |
| |
| /** |
| * <p>Gets the associated {@link JSPTranslator} for a specific JSP file.</p> |
| * <p><b>NOTE: </b><i>This does not get the persisted translator but rather the |
| * associated translator in memory</i></p> |
| * |
| * @param jspFilePath {@link IPath} to the JSP file that the associated {@link JSPTranslator} |
| * is needed for |
| * @return {@link JSPTranslator} associated with the given <code>jspFilePath</code>, or |
| * <code>null</code> if none can be found. |
| */ |
| private JSPTranslator getJSPTranslator(IPath jspFilePath) { |
| IFile jspFile = ResourcesPlugin.getWorkspace().getRoot().getFile(jspFilePath); |
| IStructuredModel model = null; |
| JSPTranslator translator = null; |
| try { |
| model = StructuredModelManager.getModelManager().getModelForRead(jspFile); |
| if(model instanceof IDOMModel) { |
| IDOMDocument doc = ((IDOMModel)model).getDocument(); |
| ModelHandlerForJSP.ensureTranslationAdapterFactory(model); |
| JSPTranslationAdapter adapter = (JSPTranslationAdapter)doc.getAdapterFor(IJSPTranslation.class); |
| |
| //don't want to persist a translator that has not already been requested |
| if(adapter != null && adapter.hasTranslation()) { |
| translator = adapter.getJSPTranslation().getTranslator(); |
| } |
| } |
| } catch (IOException e) { |
| Logger.logException("Could not get translator for " + jspFilePath + |
| " because could not read model for same.", e); |
| } catch (CoreException e) { |
| Logger.logException("Could not get translator for " + jspFilePath + |
| " because could not read model for same.", e); |
| } finally { |
| if(model != null) { |
| model.releaseFromRead(); |
| } |
| } |
| |
| return translator; |
| } |
| |
| /** |
| * <p>Persists a {@link JSPTranslator} to disk for a specific JSP file</p> |
| * |
| * @param translator {@link JSPTranslator} to persist to disk |
| * @param jspFilePath {@link IPath} to the JSP file the given <code>translator</code> is for |
| */ |
| private void persistTranslator(JSPTranslator translator, IPath jspFilePath) { |
| String persistedTranslatorFilePath = |
| getPersistedTranslatorFilePath(jspFilePath.toPortableString()); |
| try { |
| FileOutputStream fos = new FileOutputStream(persistedTranslatorFilePath); |
| ObjectOutputStream out = new ObjectOutputStream(fos); |
| out.writeObject(translator); |
| out.close(); |
| } catch (IOException e) { |
| Logger.logException("Was unable to externalize JSPTranslator " + translator + |
| " to " + persistedTranslatorFilePath, e); |
| } |
| } |
| |
| /** |
| * <p>Deletes a persisted translation for a JSP file that has been deleted</p> |
| * |
| * @param jspFilePath {@link IPath} to the JSP file that has been deleted |
| */ |
| private void deletePersistedTranslator(IPath jspFilePath) { |
| String persistedTranslatorFilePath = |
| getPersistedTranslatorFilePath(jspFilePath.toPortableString()); |
| File persistedTranslatorFile = new File(persistedTranslatorFilePath); |
| persistedTranslatorFile.delete(); |
| } |
| |
| /** |
| * <p>Renames a persisted translation for a JSP file that has moved</p> |
| * |
| * @param jspPrevFilePath {@link IPath} to the previous location of JSP file</p> |
| * @param jspNewFilePath {@link IPath} to new location of JSP file</p> |
| */ |
| private void renamePersistedTranslator(IPath jspPrevFilePath, IPath jspNewFilePath) { |
| String prevPersistedTranslatorFilePath = |
| getPersistedTranslatorFilePath(jspPrevFilePath.toPortableString()); |
| String newPersistedTranslatorFilePath = |
| getPersistedTranslatorFilePath(jspNewFilePath.toPortableString()); |
| File oldPersistedTranslatorFile = new File(prevPersistedTranslatorFilePath); |
| File newPersistedTranslatorFile = new File(newPersistedTranslatorFilePath); |
| |
| //do the move |
| oldPersistedTranslatorFile.renameTo(newPersistedTranslatorFile); |
| } |
| } |
| } |