/******************************************************************************* * Copyright (c) 2010, 2014 Spring IDE Developers * 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: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.beans.core.internal.model.namespaces; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverExtension; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.ProjectAwareUrlStreamHandlerService; import org.springframework.ide.eclipse.beans.core.internal.model.namespaces.DocumentAccessor.SchemaLocations; import org.springframework.ide.eclipse.beans.core.model.IBeansProject; import org.springframework.ide.eclipse.beans.core.namespaces.NamespaceUtils; import org.springframework.ide.eclipse.core.SpringCorePreferences; import org.springframework.ide.eclipse.core.java.JdtUtils; import org.w3c.dom.Document; /** * {@link URIResolverExtension} resolves URIs on the project classpath using the * protocol established by <code>spring.schemas</code> files. * * @author Christian Dupuis * @author Martin Lippert * @since 2.3.1 */ @SuppressWarnings({ "restriction", "deprecation" }) public class ProjectClasspathExtensibleUriResolver implements URIResolverExtension, IElementChangedListener, IPropertyChangeListener, IPreferenceChangeListener { private static final String KEY_DISABLE_CACHING_PREFERENCE = BeansCorePlugin.PLUGIN_ID + "." + BeansCorePlugin.DISABLE_CACHING_FOR_NAMESPACE_LOADING_ID; // private static Map<IProject, ProjectClasspathUriResolver> projectResolvers = new ConcurrentHashMap<IProject, ProjectClasspathUriResolver>(); private static ConcurrentMap<IProject, Future<ProjectClasspathUriResolver>> projectResolvers = new ConcurrentHashMap<IProject, Future<ProjectClasspathUriResolver>>(); public ProjectClasspathExtensibleUriResolver() { JavaCore.addElementChangedListener(this, ElementChangedEvent.POST_CHANGE); BeansCorePlugin.getDefault().getPluginPreferences() .addPropertyChangeListener(this); } /** * {@inheritDoc} */ public String resolve(IFile file, String baseLocation, String publicId, String systemId) { // systemId is already resolved; so don't touch if (systemId != null && systemId.startsWith("jar:")) { return null; } // identify the correct project IProject project = null; if (file != null) { project = getBestMatchingProject(file); } else if (baseLocation != null && baseLocation.startsWith(ProjectAwareUrlStreamHandlerService.PROJECT_AWARE_PROTOCOL_HEADER)) { String nameAndLocation = baseLocation .substring(ProjectAwareUrlStreamHandlerService.PROJECT_AWARE_PROTOCOL_HEADER .length()); String projectName = nameAndLocation.substring(0, nameAndLocation.indexOf('/')); project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); } // continue just for identified Spring projects if (project == null || BeansCorePlugin.getModel().getProject(project) == null) { return null; } if (systemId == null && file != null) { systemId = findSystemIdFromFile(file, publicId); } if (systemId == null && publicId == null) { return null; } ProjectClasspathUriResolver resolver = getProjectResolver(file, project); if (resolver != null) { String resolved = resolver.resolveOnClasspath(publicId, systemId); if (resolved != null) { resolved = ProjectAwareUrlStreamHandlerService.createProjectAwareUrl(project.getName(), resolved); } return resolved; } return null; } private IProject getBestMatchingProject(IFile file) { IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(file.getLocationURI()); if (files != null && files.length == 1) { return files[0].getProject(); } else if (files != null && files.length > 1) { IFile shortestPathFile = files[0]; int shortestPathSegmentCount = shortestPathFile.getFullPath().segmentCount(); for (int i = 1; i < files.length; i++) { int segmentCount = files[i].getFullPath().segmentCount(); if (segmentCount < shortestPathSegmentCount) { shortestPathFile = files[i]; shortestPathSegmentCount = segmentCount; } } return shortestPathFile.getProject(); } else { return null; } } private ProjectClasspathUriResolver getProjectResolver(final IFile file, final IProject project) { // no project resolver if not a spring project IBeansProject beansProject = BeansCorePlugin.getModel().getProject(project); if (beansProject == null) { return null; } if (!NamespaceUtils.useNamespacesFromClasspath(project)) { return null; } if (file != null && !checkFileExtension(file, beansProject)) { return null; } while (true) { Future<ProjectClasspathUriResolver> future = projectResolvers.get(project); if (future == null) { Callable<ProjectClasspathUriResolver> createResolver = new Callable<ProjectClasspathUriResolver>() { public ProjectClasspathUriResolver call() throws InterruptedException { ProjectClasspathUriResolver resolver = new ProjectClasspathUriResolver( project); SpringCorePreferences .getProjectPreferences(project, BeansCorePlugin.PLUGIN_ID) .getProjectPreferences().addPreferenceChangeListener(ProjectClasspathExtensibleUriResolver.this); return resolver; } }; FutureTask<ProjectClasspathUriResolver> futureTask = new FutureTask<ProjectClasspathUriResolver>(createResolver); future = projectResolvers.putIfAbsent(project, futureTask); if (future == null) { future = futureTask; futureTask.run(); } } try { return future.get(); } catch (CancellationException e) { projectResolvers.remove(project, future); return null; } catch (ExecutionException e) { return null; } catch (InterruptedException e) { return null; } } } public void elementChanged(ElementChangedEvent event) { for (IJavaElementDelta delta : event.getDelta().getAffectedChildren()) { if ((delta.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0 || (delta.getFlags() & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0) { resetForChangedElement(delta.getElement()); } else if ((delta.getFlags() & IJavaElementDelta.F_CLOSED) != 0) { resetForChangedElement(delta.getElement()); } else if ((delta.getFlags() & IJavaElementDelta.F_OPENED) != 0) { resetForChangedElement(delta.getElement()); } else if ((delta.getKind() & IJavaElementDelta.REMOVED) != 0) { resetForChangedElement(delta.getElement()); } else if ((delta.getKind() & IJavaElementDelta.ADDED) != 0) { resetForChangedElement(delta.getElement()); } } } public void propertyChange(PropertyChangeEvent event) { if (BeansCorePlugin.DISABLE_CACHING_FOR_NAMESPACE_LOADING_ID .equals(event.getProperty())) { projectResolvers.clear(); } } public void preferenceChange(PreferenceChangeEvent event) { if (KEY_DISABLE_CACHING_PREFERENCE.equals(event.getKey())) { projectResolvers.clear(); } } private void resetForChangedElement(IJavaElement element) { if (element instanceof IJavaProject) { IProject project = ((IJavaProject) element).getProject(); projectResolvers.remove(project); SpringCorePreferences .getProjectPreferences(project, BeansCorePlugin.PLUGIN_ID) .getProjectPreferences() .removePreferenceChangeListener(this); } for (IProject project : projectResolvers.keySet()) { IJavaProject javaProject = JdtUtils.getJavaProject(project); if (javaProject != null) { if (javaProject.isOnClasspath(element)) { projectResolvers.remove(project); SpringCorePreferences .getProjectPreferences(project, BeansCorePlugin.PLUGIN_ID) .getProjectPreferences() .removePreferenceChangeListener(this); } } } } /** * try to extract the system-id of the given namespace from the xml file * * @since 2.6.0 */ private String findSystemIdFromFile(IFile file, String publicIc) { InputStream contents = null; try { contents = file.getContents(); DocumentBuilderFactory builderFactory = DocumentBuilderFactory .newInstance(); builderFactory.setValidating(false); builderFactory.setNamespaceAware(true); builderFactory.setFeature("http://xml.org/sax/features/validation", false); builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); DocumentBuilder builder = builderFactory.newDocumentBuilder(); Document doc = builder.parse(contents); DocumentAccessor accessor = new DocumentAccessor(); accessor.pushDocument(doc); SchemaLocations locations = accessor.getCurrentSchemaLocations(); String location = locations.getSchemaLocation(publicIc); return location; } catch (Exception e) { // do nothing, systemId cannot be identified } finally { if (contents != null) { try { contents.close(); } catch (IOException e) { // do nothing, systemId cannot be identified } } } return null; } /** * Check that the file has a valid file extension. */ private boolean checkFileExtension(IFile file, IBeansProject project) { if (project.getConfigSuffixes() != null) { for (String extension : project.getConfigSuffixes()) { if (file.getName().endsWith(extension)) { return true; } } } if (file.getName().endsWith(".xsd")) { return true; } return false; } }