/******************************************************************************* * Copyright (c) 2008, 2013 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.model.locate; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.xml.core.internal.document.DOMModelImpl; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.springframework.beans.factory.xml.NamespaceHandlerResolver; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.internal.model.namespaces.DelegatingNamespaceHandlerResolver; import org.springframework.ide.eclipse.beans.core.namespaces.NamespaceUtils; import org.springframework.ide.eclipse.core.java.JdtUtils; import org.springframework.util.StringUtils; import org.springsource.ide.eclipse.commons.core.SpringCoreUtils; /** * Basic {@link IBeansConfigLocator} that is capable for scanning an * {@link IProject} or {@link IJavaProject} for Spring XML configuration files. * <p> * Only those XML files that have any known namespace uri at the root element * level are being considered to be a suitable candidate. * * @author Christian Dupuis * @since 2.0.5 */ @SuppressWarnings("restriction") public class ProjectScanningBeansConfigLocator extends AbstractJavaProjectPathMatchingBeansConfigLocator { /** Ant-style that matches on every XML file */ private String ALLOWED_FILE_PATTERN = "**/*"; /** * Internal cache for {@link NamespaceHandlerResolver}s keyed by their * {@link IProject} */ private Map<IProject, NamespaceHandlerResolver> namespaceResoverCache = new HashMap<IProject, NamespaceHandlerResolver>(); /** Configured file patters derived from the configured file patterns */ private Set<String> configuredFilePatterns = null; /** Configured file extensions from the dialog */ private Set<String> configuredFileExtensions = null; /** The project this locator operates on */ private IProject project = null; /** * Constructor taking a string of CSV file extensions * * @param configuredFileSuffixes */ public ProjectScanningBeansConfigLocator(String configuredFileSuffixes) { configuredFilePatterns = new ConcurrentSkipListSet<String>(); configuredFileExtensions = new ConcurrentSkipListSet<String>(); for (String filePattern : StringUtils .commaDelimitedListToStringArray(configuredFileSuffixes)) { filePattern = filePattern.trim(); int ix = filePattern.lastIndexOf('.'); if (ix != -1) { configuredFileExtensions.add(filePattern.substring(ix + 1)); } else { configuredFileExtensions.add(filePattern); } configuredFilePatterns.add(ALLOWED_FILE_PATTERN + filePattern); } } /** * As this locator is not intended to be used at runtime, we don't need to * listen to any resource changes. */ @Override public boolean requiresRefresh(IFile file) { return false; } /** * Supports both an normal {@link IProject} and a {@link IJavaProject} but * it needs to have the Spring nature. */ @Override public boolean supports(IProject project) { return SpringCoreUtils.isSpringProject(project); } /** * Returns a {@link NamespaceHandlerResolver} for the given {@link IProject} * . First looks in the {@link #namespaceResoverCache cache} before creating * a new instance. */ protected NamespaceHandlerResolver getNamespaceHandlerResolver( IProject project) { if (!namespaceResoverCache.containsKey(project)) { namespaceResoverCache.put(project, new DelegatingNamespaceHandlerResolver( NamespaceHandlerResolver.class.getClassLoader(), null)); } return namespaceResoverCache.get(project); } /** * Filters out every {@link IFile} which is has unknown root elements in its * XML content. */ @Override protected Set<IFile> filterMatchingFiles(Set<IFile> files) { // if project is a java project remove bin dirs from the list Set<String> outputDirectories = new HashSet<String>(); IJavaProject javaProject = JdtUtils.getJavaProject(project); if (javaProject != null) { try { // add default output directory outputDirectories.add(javaProject.getOutputLocation() .toString()); // add source folder specific output directories for (IClasspathEntry entry : javaProject.getRawClasspath()) { if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE && entry.getOutputLocation() != null) { outputDirectories.add(entry.getOutputLocation() .toString()); } } } catch (JavaModelException e) { BeansCorePlugin.log(e); } } Set<IFile> detectedFiles = new LinkedHashSet<IFile>(); for (IFile file : files) { boolean skip = false; // first check if the file sits in an output directory String path = file.getFullPath().toString(); for (String outputDirectory : outputDirectories) { if (path.startsWith(outputDirectory)) { skip = true; } } if (skip) { continue; } // check if the file is known Spring xml file IStructuredModel model = null; try { try { model = StructuredModelManager.getModelManager() .getExistingModelForRead(file); } catch (RuntimeException e) { // sometimes WTP throws a NPE in concurrency situations } if (model == null) { model = StructuredModelManager.getModelManager() .getModelForRead(file); } if (model != null) { IDOMDocument document = ((DOMModelImpl) model) .getDocument(); if (document != null && document.getDocumentElement() != null) { String namespaceUri = document.getDocumentElement() .getNamespaceURI(); if (applyNamespaceFilter(file, namespaceUri)) { detectedFiles.add(file); } } } } catch (IOException e) { BeansCorePlugin.log(e); } catch (CoreException e) { BeansCorePlugin.log(e); } finally { if (model != null) { model.releaseFromRead(); } } } return detectedFiles; } protected boolean applyNamespaceFilter(IFile file, String namespaceUri) { return (namespaceUri != null && (NamespaceUtils.DEFAULT_NAMESPACE_URI .equals(namespaceUri) || getNamespaceHandlerResolver( file.getProject()).resolve(namespaceUri) != null)); } /** * {@inheritDoc} */ @Override protected Set<String> getAllowedFilePatterns() { return configuredFilePatterns; } /** * {@inheritDoc} */ @Override protected Set<String> getAllowedFileExtensions() { return configuredFileExtensions; } /** * Returns the root directories to scan. */ @Override protected Set<IPath> getRootDirectories(IProject project) { this.project = project; Set<IPath> rootDirectories = new LinkedHashSet<IPath>(); rootDirectories.add(project.getFullPath()); return rootDirectories; } }