/*******************************************************************************
* Copyright (c) 2007, 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.validation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.ide.eclipse.beans.core.DefaultBeanDefinitionRegistry;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansConfigSet;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.internal.model.validation.rules.ValidationRuleUtils;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfigSet;
import org.springframework.ide.eclipse.beans.core.model.IBeansImport;
import org.springframework.ide.eclipse.beans.core.model.validation.IBeansValidationContext;
import org.springframework.ide.eclipse.beans.core.namespaces.ToolAnnotationUtils;
import org.springframework.ide.eclipse.beans.core.namespaces.ToolAnnotationUtils.ToolAnnotationData;
import org.springframework.ide.eclipse.core.java.IProjectClassLoaderSupport;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.core.model.IResourceModelElement;
import org.springframework.ide.eclipse.core.model.validation.AbstractValidationContext;
import org.springframework.ide.eclipse.core.model.validation.IValidationProblemMarker;
import org.springframework.ide.eclipse.core.model.validation.IValidationRule;
import org.springframework.ide.eclipse.core.model.validation.ValidationProblem;
import org.springframework.ide.eclipse.core.model.validation.ValidationProblemAttribute;
import org.springframework.ide.eclipse.core.type.asm.CachingClassReaderFactory;
import org.springframework.ide.eclipse.core.type.asm.ClassReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Context that gets passed to an {@link IValidationRule}, encapsulating all relevant information used during
* validation.
* @author Torsten Juergeleit
* @author Christian Dupuis
* @since 2.0
*/
public class BeansValidationContext extends AbstractValidationContext implements IBeansValidationContext {
private static final char KEY_SEPARATOR_CHAR = '/';
private Map<String, Set<BeanDefinition>> beanLookupCache;
private ClassReaderFactory classReaderFactory;
private BeanDefinitionRegistry completeRegistry;
private BeanDefinitionRegistry incompleteRegistry;
private IProjectClassLoaderSupport projectClassLoaderSupport;
private final Map<AttributeDescriptor, List<ToolAnnotationData>> toolAnnotationLookupCache;
public BeansValidationContext(IBeansConfig config, IResourceModelElement contextElement) {
super(config, contextElement);
this.incompleteRegistry = createRegistry(config, contextElement, false);
this.completeRegistry = createRegistry(config, contextElement, true);
this.beanLookupCache = new HashMap<String, Set<BeanDefinition>>();
this.toolAnnotationLookupCache = new HashMap<AttributeDescriptor, List<ToolAnnotationData>>();
}
/**
* {@inheritDoc}
*/
public synchronized ClassReaderFactory getClassReaderFactory() {
if (this.classReaderFactory == null) {
this.classReaderFactory = new CachingClassReaderFactory(JdtUtils.getClassLoader(getRootElement()
.getElementResource().getProject(), null));
}
return this.classReaderFactory;
}
/**
* {@inheritDoc}
*/
public BeanDefinitionRegistry getCompleteRegistry() {
return completeRegistry;
}
/**
* {@inheritDoc}
*/
public BeanDefinitionRegistry getIncompleteRegistry() {
return incompleteRegistry;
}
/**
* {@inheritDoc}
*/
public synchronized IProjectClassLoaderSupport getProjectClassLoaderSupport() {
if (this.projectClassLoaderSupport == null) {
this.projectClassLoaderSupport = JdtUtils.getProjectClassLoaderSupport(getRootElementProject(), null);
}
return this.projectClassLoaderSupport;
}
/**
* {@inheritDoc}
*/
public Set<BeanDefinition> getRegisteredBeanDefinition(String beanName, String beanClass) {
Assert.notNull(beanName);
Assert.notNull(beanClass);
String key = beanClass + KEY_SEPARATOR_CHAR + beanName;
if (beanLookupCache.containsKey(key)) {
return beanLookupCache.get(key);
}
Set<BeanDefinition> bds = ValidationRuleUtils.getBeanDefinitions(beanName, beanClass, this);
// as we don't use a Hashtable we can insert null values
beanLookupCache.put(key, bds);
return bds;
}
/**
* {@inheritDoc}
*/
public IProject getRootElementProject() {
return (getRootElement().getElementResource() != null ? getRootElement().getElementResource().getProject()
: null);
}
/**
* {@inheritDoc}
*/
public IResource getRootElementResource() {
return getRootElement().getElementResource();
}
public synchronized List<ToolAnnotationData> getToolAnnotation(Node n, String attributeName) {
AttributeDescriptor descriptor = AttributeDescriptor.create(n, attributeName);
if (toolAnnotationLookupCache.containsKey(descriptor)) {
return toolAnnotationLookupCache.get(descriptor);
}
// Search for tool annotations
List<ToolAnnotationData> annotationDatas = new ArrayList<ToolAnnotationData>();
List<Element> appInfoElements = ToolAnnotationUtils.getApplicationInformationElements(n, attributeName);
for (Element elem : appInfoElements) {
NodeList children = elem.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node annotation = children.item(j);
if (annotation.getNodeType() == Node.ELEMENT_NODE
&& ToolAnnotationUtils.ANNOTATION_ELEMENT.equals(annotation.getLocalName())
&& ToolAnnotationUtils.TOOL_NAMESPACE_URI.equals(annotation.getNamespaceURI())) {
ToolAnnotationData annotationData = ToolAnnotationUtils.getToolAnnotationData(annotation);
if (annotationData != null) {
annotationDatas.add(annotationData);
}
}
}
}
// Add to internal cache
toolAnnotationLookupCache.put(descriptor, annotationDatas);
// / Return found annoatations
return annotationDatas;
}
/**
* {@inheritDoc}
*/
public boolean isBeanRegistered(String beanName, String beanClass) {
Set<BeanDefinition> bds = getRegisteredBeanDefinition(beanName, beanClass);
return bds != null && bds.size() > 0;
}
private BeanDefinitionRegistry createRegistry(IBeansConfig config, IResourceModelElement contextElement,
boolean fillCompletely) {
DefaultBeanDefinitionRegistry registry = new DefaultBeanDefinitionRegistry();
if (contextElement instanceof BeansConfigSet) {
IBeansConfigSet configSet = (IBeansConfigSet) contextElement;
if (fillCompletely) {
registry.setAllowAliasOverriding(true);
registry.setAllowBeanDefinitionOverriding(true);
}
else {
registry.setAllowAliasOverriding(configSet.isAllowAliasOverriding());
registry.setAllowBeanDefinitionOverriding(configSet.isAllowBeanDefinitionOverriding());
}
for (IBeansConfig csConfig : configSet.getConfigs()) {
if (!fillCompletely && config.equals(csConfig)) {
break;
}
BeansModelUtils.register(configSet, csConfig, registry);
}
}
else if (contextElement instanceof IBeansConfig && !config.equals(contextElement)) {
registry.setAllowAliasOverriding(true);
registry.setAllowBeanDefinitionOverriding(true);
if (fillCompletely) {
BeansModelUtils.register(null, (IBeansConfig) contextElement, registry);
}
}
else {
registry.setAllowAliasOverriding(false);
registry.setAllowBeanDefinitionOverriding(false);
if (fillCompletely) {
BeansModelUtils.register(null, config, registry);
}
}
return registry;
}
/**
* {@inheritDoc}
*/
@Override
protected Set<ValidationProblem> createProblems(IResourceModelElement element, String problemId, int severity,
String message, ValidationProblemAttribute... attributes) {
Set<ValidationProblem> problems = super.createProblems(element, problemId, severity, message, attributes);
IResource resource = element.getElementResource();
// Check if error or warning on imported resource exists
if (!resource.equals(getRootElementResource())) {
IBeansImport beansImport = BeansModelUtils.getParentOfClass(element, IBeansImport.class);
while (beansImport != null) {
if (severity == IValidationProblemMarker.SEVERITY_ERROR) {
problems.add(createProblem(beansImport, "", IValidationProblemMarker.SEVERITY_ERROR,
"Validation error occured in imported configuration file '"
+ element.getElementResource().getProjectRelativePath().toString() + "'"));
}
else if (severity == IValidationProblemMarker.SEVERITY_WARNING) {
problems.add(createProblem(beansImport, "", IValidationProblemMarker.SEVERITY_WARNING,
"Validation warning occured in imported configuration file '"
+ element.getElementResource().getProjectRelativePath().toString() + "'"));
}
beansImport = BeansModelUtils.getParentOfClass(beansImport, IBeansImport.class);
}
}
return problems;
}
/**
* {@inheritDoc}
*/
@Override
protected StringBuilder decorateErrorMessage(StringBuilder builder) {
IResourceModelElement context = getContextElement();
if (context != null && context.getElementResource() != null) {
String projectName = context.getElementResource().getProject().getName();
// Only decorate the context if the context element is a config set as otherwise the context
// should be clear from the marker/problems view -> it is the file being displayed
if (context instanceof IBeansConfigSet) {
builder.append(String.format(" [config set: %s/%s]", projectName, context.getElementName()));
}
}
return builder;
}
static class AttributeDescriptor {
private final String attributeName;
private final String localName;
private final String namespaceUri;
private AttributeDescriptor(String namespaceUri, String localName, String attributeName) {
this.namespaceUri = namespaceUri;
this.localName = localName;
this.attributeName = attributeName;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AttributeDescriptor)) {
return false;
}
AttributeDescriptor other = (AttributeDescriptor) obj;
if (!ObjectUtils.nullSafeEquals(namespaceUri, other.namespaceUri)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(localName, other.localName)) {
return false;
}
return ObjectUtils.nullSafeEquals(attributeName, attributeName);
}
@Override
public int hashCode() {
int hashCode = 7;
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(namespaceUri);
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(localName);
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(attributeName);
return hashCode;
}
public static AttributeDescriptor create(Node n, String attributeName) {
return new AttributeDescriptor(n.getNamespaceURI(), n.getLocalName(), attributeName);
}
}
}