/******************************************************************************* * Copyright (c) 2009, 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.internal.model.namespaces; import java.io.IOException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.osgi.framework.Bundle; 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.util.StringUtils; /** * Extension to Spring DM's {@link NamespacePlugins} class that handles registering of XSDs in Eclipse' XML Catalog and * builds an internal representation of {@link INamespaceDefinition}s. * * @author Christian Dupuis * @author Martin Lippert * @since 2.2.5 */ public class ToolingAwareNamespacePlugins extends NamespacePlugins implements INamespaceDefinitionResolver { private static final String META_INF = "META-INF/"; private static final String SPRING_SCHEMAS = "spring.schemas"; private static final String SPRING_TOOLING = "spring.tooling"; /** Namespace definitions keyed by the namespace uri */ private volatile Map<String, NamespaceDefinition> namespaceDefinitionRegistry = new HashMap<String, NamespaceDefinition>(); /** Namespace definition set keyed by the registering bundle */ private volatile Map<Bundle, Set<NamespaceDefinition>> namespaceDefinitionsByBundle = new HashMap<Bundle, Set<NamespaceDefinition>>(); /** Listeners to inform about namespace changes */ private volatile Set<INamespaceDefinitionListener> namespaceDefinitionListeners = Collections .synchronizedSet(new HashSet<INamespaceDefinitionListener>()); /** * {@inheritDoc} */ @Override void addPlugin(Bundle bundle, boolean lazyBundle, boolean applyCondition) { super.addPlugin(bundle, lazyBundle, applyCondition); addNamespaceDefinition(bundle); } /** * {@inheritDoc} */ @Override boolean removePlugin(Bundle bundle) { removeNamespaceDefinition(bundle); return super.removePlugin(bundle); } /** * {@inheritDoc} */ public Set<INamespaceDefinition> getNamespaceDefinitions() { return new HashSet<INamespaceDefinition>(namespaceDefinitionRegistry.values()); } /** * {@inheritDoc} */ public INamespaceDefinition resolveNamespaceDefinition(String namespaceUri) { return namespaceDefinitionRegistry.get(namespaceUri); } /** * Register the XML namespace catalog and build the {@link INamespaceDefinition}s. */ private void addNamespaceDefinition(Bundle bundle) { Set<NamespaceDefinition> namespaceDefinitions = new HashSet<NamespaceDefinition>(); this.namespaceDefinitionsByBundle.put(bundle, namespaceDefinitions); Enumeration<URL> schemas = bundle.findEntries(META_INF, SPRING_SCHEMAS, false); if (schemas != null) { while (schemas.hasMoreElements()) { Properties props = loadProperties(schemas.nextElement()); for (Object xsd : props.keySet()) { String key = xsd.toString(); String namespaceUri = TargetNamespaceScanner.getTargetNamespace(bundle.getEntry(props.getProperty(key))); String icon = null; String prefix = null; String name = null; if (StringUtils.hasText(namespaceUri)) { Enumeration<URL> tooling = bundle.findEntries(META_INF, SPRING_TOOLING, false); if (tooling != null) { while (tooling.hasMoreElements()) { Properties toolingProps = loadProperties(tooling.nextElement()); icon = toolingProps.getProperty(namespaceUri + "@icon"); prefix = toolingProps.getProperty(namespaceUri + "@prefix"); name = toolingProps.getProperty(namespaceUri + "@name"); } } if (namespaceDefinitionRegistry.containsKey(namespaceUri)) { namespaceDefinitionRegistry.get(namespaceUri).addSchemaLocation(key); namespaceDefinitionRegistry.get(namespaceUri).addUri(props.getProperty(key)); } else { NamespaceDefinition namespaceDefinition = new NamespaceDefinition(props); namespaceDefinition.setName(name); namespaceDefinition.setPrefix(prefix); namespaceDefinition.setIconPath(icon); namespaceDefinition.addSchemaLocation(key); namespaceDefinition.setBundle(bundle); namespaceDefinition.setNamespaceUri(namespaceUri); namespaceDefinition.addUri(props.getProperty(key)); registerNamespaceDefinition(namespaceUri, namespaceDefinition); namespaceDefinitions.add(namespaceDefinition); } } } } } } private void registerNamespaceDefinition(String uri, final NamespaceDefinition definition) { namespaceDefinitionRegistry.put(uri, definition); for (final INamespaceDefinitionListener listener : namespaceDefinitionListeners) { SafeRunner.run(new ISafeRunnable() { public void run() throws Exception { listener.onNamespaceDefinitionRegistered(new INamespaceDefinitionListener.NamespaceDefinitionChangeEvent( definition, null)); } public void handleException(Throwable exception) { BeansCorePlugin.log(exception); } }); } } /** * Fail safe loading of a {@link Properties} from an {@link URL}. Returns an empty {@link Properties} if the an * {@link IOException} occurs. */ private Properties loadProperties(URL url) { Properties toolingProps = new Properties(); try { toolingProps.load(url.openStream()); } catch (IOException e) { BeansCorePlugin.log(e); } return toolingProps; } /** * Removes any registered {@link INamespaceDefinition}s and XML catalog entries. */ private void removeNamespaceDefinition(Bundle bundle) { if (namespaceDefinitionsByBundle.containsKey(bundle)) { for (NamespaceDefinition definition : namespaceDefinitionsByBundle.get(bundle)) { unregisterNamespaceDefinition(definition); } namespaceDefinitionsByBundle.remove(bundle); } } private void unregisterNamespaceDefinition(final NamespaceDefinition definition) { namespaceDefinitionRegistry.remove(definition.getNamespaceUri()); for (final INamespaceDefinitionListener listener : namespaceDefinitionListeners) { SafeRunner.run(new ISafeRunnable() { public void run() throws Exception { listener.onNamespaceDefinitionUnregistered(new INamespaceDefinitionListener.NamespaceDefinitionChangeEvent( definition, null)); } public void handleException(Throwable exception) { BeansCorePlugin.log(exception); } }); } } public void unregisterNamespaceDefinitionListener(INamespaceDefinitionListener listener) { namespaceDefinitionListeners.remove(listener); } public void registerNamespaceDefinitionListener(INamespaceDefinitionListener listener) { if (!namespaceDefinitionListeners.contains(listener)) { namespaceDefinitionListeners.add(listener); } } static class Version implements Comparable<Version> { private static final String MINIMUM_VERSION_STRING = "0"; public static final Version MINIMUM_VERSION = new Version(MINIMUM_VERSION_STRING); private final org.osgi.framework.Version version; public Version(String v) { org.osgi.framework.Version tempVersion = null; try { tempVersion = org.osgi.framework.Version.parseVersion(v); } catch (Exception e) { // make sure that we don't crash on any new version numbers format that we don't support BeansCorePlugin.log("Cannot convert schema vesion", e); tempVersion = org.osgi.framework.Version.parseVersion(MINIMUM_VERSION_STRING); } this.version = tempVersion; } public int compareTo(Version v2) { return this.version.compareTo(v2.version); } } }