/******************************************************************************* * Copyright (c) 2010, 2011 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.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.model.INamespaceDefinition; import org.springframework.ide.eclipse.beans.core.model.INamespaceDefinitionListener; import org.springframework.ide.eclipse.beans.core.model.INamespaceDefinitionResolver; 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.springframework.util.CollectionUtils; import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils; /** * {@link INamespaceDefinitionResolver} that resolves {@link INamespaceDefinition}s from the project's classpath as * well. * <p> * Note: this implementation is currently not in use * @author Christian Dupuis * @author Martin Lippert * @since 2.3.1 * @see BeansCorePlugin#getNamespaceDefinitionResolver(IProject) */ @SuppressWarnings("deprecation") public class ProjectClasspathNamespaceDefinitionResolver implements INamespaceDefinitionResolver { public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas"; public static final String DEFAULT_TOOLING_MAPPINGS_LOCATION = "META-INF/spring.tooling"; private Map<String, NamespaceDefinition> namespaceDefinitionRegistry = null; private final IProject project; private EnablementPropertyChangeListener propertyChangeListener; private final INamespaceDefinitionResolver resolver; public ProjectClasspathNamespaceDefinitionResolver(IProject project) { this.project = project; this.resolver = BeansCorePlugin.getNamespaceDefinitionResolver(); init(); registerListeners(); } /** * Dispose this resolver instance. */ public void dispose() { unRegisterListeners(); } /** * {@inheritDoc} */ public Set<INamespaceDefinition> getNamespaceDefinitions() { return new HashSet<INamespaceDefinition>(namespaceDefinitionRegistry.values()); } /** * {@inheritDoc} */ public INamespaceDefinition resolveNamespaceDefinition(String namespaceUri) { return namespaceDefinitionRegistry.get(namespaceUri); } /** * Load all {@link NamespaceDefinition}s from the project classpath. Also handle extraction of icon file. */ private void init() { // long start = System.currentTimeMillis(); this.namespaceDefinitionRegistry = new ConcurrentHashMap<String, NamespaceDefinition>(); // Add in namespace definitions from the classpath if (NamespaceUtils.useNamespacesFromClasspath(project)) { ClassLoader cls = JdtUtils.getClassLoader(project, null); Map<String, String> handlerMappings = new HashMap<String, String>(); Map<String, String> toolingMappings = new HashMap<String, String>(); Properties schemaMappings = new Properties(); try { Properties mappings = PropertiesLoaderUtils.loadAllProperties(DEFAULT_HANDLER_MAPPINGS_LOCATION, cls); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); schemaMappings = PropertiesLoaderUtils.loadAllProperties(DEFAULT_SCHEMA_MAPPINGS_LOCATION, cls); mappings = PropertiesLoaderUtils.loadAllProperties(DEFAULT_TOOLING_MAPPINGS_LOCATION, cls); CollectionUtils.mergePropertiesIntoMap(mappings, toolingMappings); } catch (IOException e) { } for (Object xsd : schemaMappings.keySet()) { String key = xsd.toString(); String schemaUri = schemaMappings.getProperty(key); URL url = cls.getResource(schemaUri); // fallback, if schema location starts with / and therefore fails to be found by classloader if (url == null && schemaUri.startsWith("/")) { schemaUri = schemaUri.substring(1); url = cls.getResource(schemaUri); } if (url == null) { continue; } String namespaceUri = TargetNamespaceScanner.getTargetNamespace(url); if (StringUtils.hasText(namespaceUri)) { String icon = toolingMappings.get(namespaceUri + "@icon"); String prefix = toolingMappings.get(namespaceUri + "@prefix"); String name = toolingMappings.get(namespaceUri + "@name"); if (namespaceDefinitionRegistry.containsKey(namespaceUri)) { namespaceDefinitionRegistry.get(namespaceUri).addSchemaLocation(key); namespaceDefinitionRegistry.get(namespaceUri).addUri(schemaUri); } else { File iconFile = extractIcon(namespaceUri, icon, cls); NamespaceDefinition namespaceDefinition = new ExternalImageNamespaceDefinition(schemaMappings, iconFile); namespaceDefinition.setName(name); namespaceDefinition.setPrefix(prefix); namespaceDefinition.setIconPath(icon); namespaceDefinition.addSchemaLocation(key); namespaceDefinition.setNamespaceUri(namespaceUri); namespaceDefinition.addUri(schemaUri); namespaceDefinitionRegistry.put(namespaceUri, namespaceDefinition); } } } } else { for (INamespaceDefinition namespaceDefinition : resolver.getNamespaceDefinitions()) { namespaceDefinitionRegistry.put(namespaceDefinition.getNamespaceUri(), (NamespaceDefinition) namespaceDefinition); } } // System.out.println(String.format("-- loading of namespace definitions took '%s'ms", // (System.currentTimeMillis() - start))); } /** * Extract icon files from the given classloader and store it on the filesystem for later use. */ private File extractIcon(String namespaceUri, String icon, ClassLoader cls) { if (StringUtils.hasLength(icon)) { try { File iconDir = BeansCorePlugin.getDefault().getStateLocation().append("images").toFile(); if (!iconDir.exists()) { iconDir.mkdirs(); iconDir.deleteOnExit(); } int ix = icon.lastIndexOf('.'); File iconFile = null; if (ix > 0) { iconFile = new File(iconDir, Integer.toString(namespaceUri.hashCode()) + icon.substring(ix)); } else { iconFile = new File(iconDir, Integer.toString(namespaceUri.hashCode())); } if (iconFile.exists()) { return iconFile; } FileCopyUtils.copy(cls.getResourceAsStream(icon), new FileOutputStream(iconFile)); return iconFile; } catch (Exception e) { BeansCorePlugin.log( String.format("Error extracting icon file '%s' for namespace '%s'", icon, namespaceUri), e); return null; } } return null; } /** * Register the {@link EnablementPropertyChangeListener} listener to get notifications on changes to the project * preferences. */ private void registerListeners() { propertyChangeListener = new EnablementPropertyChangeListener(); BeansCorePlugin.getDefault().getPluginPreferences().addPropertyChangeListener(propertyChangeListener); SpringCorePreferences.getProjectPreferences(project, BeansCorePlugin.PLUGIN_ID).getProjectPreferences() .addPreferenceChangeListener(propertyChangeListener); BeansCorePlugin.registerNamespaceDefinitionListener(propertyChangeListener); } /** * Unregister the {@link EnablementPropertyChangeListener}. */ private void unRegisterListeners() { BeansCorePlugin.getDefault().getPluginPreferences().removePropertyChangeListener(propertyChangeListener); SpringCorePreferences.getProjectPreferences(project, BeansCorePlugin.PLUGIN_ID).getProjectPreferences() .removePreferenceChangeListener(propertyChangeListener); BeansCorePlugin.unregisterNamespaceDefinitionListener(propertyChangeListener); } /** * {@link IPropertyChangeListener} and {@link IPreferenceChangeListener} implementation that detects changes to the * classpath property. * @since 2.5.0 */ private class EnablementPropertyChangeListener implements IPropertyChangeListener, IPreferenceChangeListener, INamespaceDefinitionListener { private static final String KEY = BeansCorePlugin.PLUGIN_ID + "." + BeansCorePlugin.LOAD_NAMESPACEHANDLER_FROM_CLASSPATH_ID; private final String nodePath = "/project/" + project.getName() + "/" + BeansCorePlugin.PLUGIN_ID; /** * {@inheritDoc} */ public void preferenceChange(PreferenceChangeEvent event) { if (KEY.equals(event.getKey()) && nodePath.equals(event.getNode().absolutePath())) { init(); } } /** * {@inheritDoc} */ public void propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent event) { if (BeansCorePlugin.LOAD_NAMESPACEHANDLER_FROM_CLASSPATH_ID.equals(event.getProperty())) { init(); } } /** * {@inheritDoc} */ public void onNamespaceDefinitionRegistered(NamespaceDefinitionChangeEvent event) { if (!NamespaceUtils.useNamespacesFromClasspath(project)) { init(); } } /** * {@inheritDoc} */ public void onNamespaceDefinitionUnregistered(NamespaceDefinitionChangeEvent event) { if (!NamespaceUtils.useNamespacesFromClasspath(project)) { init(); } } } /** * Extension to {@link NamespaceDefinition} that handles loading of referenced namespace icons from extracted files. * @since 2.5.0 */ private class ExternalImageNamespaceDefinition extends NamespaceDefinition { private final File iconFile; public ExternalImageNamespaceDefinition(Properties uriMapping, File iconFile) { super(uriMapping); this.iconFile = iconFile; } /** * {@inheritDoc} */ @Override public InputStream getIconStream() { if (iconFile != null && iconFile.exists() && iconFile.canRead()) { try { return new FileInputStream(iconFile); } catch (FileNotFoundException e) { return null; } } return null; } } }