/*******************************************************************************
* Copyright (c) 2011, 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.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.core.resources.IProject;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.ProjectAwareUrlStreamHandlerService;
import org.springframework.ide.eclipse.beans.core.namespaces.NamespaceUtils;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.util.CollectionUtils;
/**
* resolves URIs on the project classpath using the protocol established by
* <code>spring.schemas</code> files.
*
* @author Martin Lippert
* @since 2.7.0
*/
public class ProjectClasspathUriResolver {
private final IProject project;
private boolean disableCaching;
private Map<String, String> typePublic;
private Map<String, String> typeUri;
private Map<String, String> schemaMappings;
public ProjectClasspathUriResolver(IProject project) {
this.project = project;
this.disableCaching = NamespaceUtils
.disableCachingForNamespaceLoadingFromClasspath(project);
if (!disableCaching) {
init();
}
}
/**
* Resolves the given <code>systemId</code> on the classpath configured by
* the <code>file</code>'s project.
*/
public String resolveOnClasspath(String publicId, String systemId) {
if (disableCaching) {
return resolveOnClasspathAndSourceFolders(publicId, systemId);
}
return resolveOnClasspathOnly(publicId, systemId);
}
private String resolveOnClasspathAndSourceFolders(String publicId,
String systemId) {
ClassLoader classLoader = JdtUtils.getClassLoader(project, null);
Map<String, String> mappings = getSchemaMappings(classLoader);
if (mappings != null && systemId != null
&& mappings.containsKey(systemId)) {
String xsdPath = mappings.get(systemId);
return resolveXsdPathOnClasspath(xsdPath, classLoader);
}
return null;
}
private String resolveOnClasspathOnly(String publicId, String systemId) {
String resolved = null;
if (systemId != null) {
resolved = typeUri.get(systemId);
}
if (resolved == null && publicId != null) {
if (!(systemId != null && systemId.endsWith(".xsd"))) {
resolved = typePublic.get(publicId);
}
}
return resolved;
}
private void init() {
this.typePublic = new ConcurrentHashMap<String, String>();
this.typeUri = new ConcurrentHashMap<String, String>();
Map<String, NamespaceDefinition> namespaceDefinitionRegistry = new HashMap<String, NamespaceDefinition>();
ClassLoader classLoader = JdtUtils.getClassLoader(project, null);
schemaMappings = getSchemaMappings(classLoader);
if (schemaMappings != null) {
for (String key : schemaMappings.keySet()) {
String path = schemaMappings.get(key);
// add the resolved path to the list of uris
String resolvedPath = resolveXsdPathOnClasspath(path,
classLoader);
if (resolvedPath != null) {
typeUri.put(key, resolvedPath);
// collect base information to later extract the default uri
String namespaceUri = getTargetNamespace(resolvedPath);
if (namespaceDefinitionRegistry.containsKey(namespaceUri)) {
namespaceDefinitionRegistry.get(namespaceUri)
.addSchemaLocation(key);
namespaceDefinitionRegistry.get(namespaceUri).addUri(
path);
} else {
NamespaceDefinition namespaceDefinition = new NamespaceDefinition(
null);
namespaceDefinition.addSchemaLocation(key);
namespaceDefinition.setNamespaceUri(namespaceUri);
namespaceDefinition.addUri(path);
namespaceDefinitionRegistry.put(namespaceUri,
namespaceDefinition);
}
}
}
// Add catalog entry to namespace uri
for (NamespaceDefinition definition : namespaceDefinitionRegistry
.values()) {
String namespaceKey = definition.getNamespaceUri();
String defaultUri = definition.getDefaultUri();
String resolvedPath = resolveXsdPathOnClasspath(defaultUri,
classLoader);
if (resolvedPath != null) {
typePublic.put(namespaceKey, resolvedPath);
}
}
}
}
/**
* Returns the target namespace URI of the XSD identified by the given
* <code>resolvedPath</code>.
*/
private String getTargetNamespace(String resolvedPath) {
if (resolvedPath == null) {
return null;
}
try {
URL url = new URI(
ProjectAwareUrlStreamHandlerService.createProjectAwareUrl(
project.getName(), resolvedPath)).toURL();
return TargetNamespaceScanner.getTargetNamespace(url);
} catch (IOException e) {
BeansCorePlugin.log(e);
} catch (URISyntaxException e) {
BeansCorePlugin.log(e);
}
return null;
}
/**
* Loads all schema mappings from all <code>spring.schemas</code> files on
* the project classpath.
*
* @param classLoader
* The classloader that is used to load the properties
*/
private Map<String, String> getSchemaMappings(ClassLoader classLoader) {
Map<String, String> handlerMappings = new ConcurrentHashMap<String, String>();
try {
Properties mappings = PropertiesLoaderUtils
.loadAllProperties(
ProjectClasspathNamespaceDefinitionResolver.DEFAULT_SCHEMA_MAPPINGS_LOCATION,
classLoader);
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
} catch (IOException ex) {
// We can ignore this as we simply don't find the xsd file then.
}
return handlerMappings;
}
private String resolveXsdPathOnClasspath(String xsdPath, ClassLoader cls) {
URL url = cls.getResource(xsdPath);
// fallback, if schema location starts with / and therefore fails to be
// found by classloader
if (url == null && xsdPath.startsWith("/")) {
xsdPath = xsdPath.substring(1);
url = cls.getResource(xsdPath);
}
return url == null ? null : xsdPath;
}
}