/*******************************************************************************
* Copyright (c) 2008, 2010 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.validation;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
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.IDOMNode;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.internal.model.validation.BeansValidationContext;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IBeansImport;
import org.springframework.ide.eclipse.beans.core.model.IBeansModelElement;
import org.springframework.ide.eclipse.beans.core.model.IImportedBeansConfig;
import org.springframework.ide.eclipse.beans.core.namespaces.ToolAnnotationUtils.ToolAnnotationData;
import org.springframework.ide.eclipse.core.internal.model.validation.ValidationRuleDefinition;
import org.springframework.ide.eclipse.core.java.IProjectClassLoaderSupport;
import org.springframework.ide.eclipse.core.model.IModelElement;
import org.springframework.ide.eclipse.core.model.IResourceModelElement;
import org.springframework.ide.eclipse.core.model.validation.IValidationContext;
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.ClassReaderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* {@link IValidationRule} implementation that allows to validate on the raw XML content in the context of the bean
* validation request.
* @author Christian Dupuis
* @since 2.0.4
* @see #supports(Node)
* @see #validate(Node,IBeansValidationContext)
*/
@SuppressWarnings("restriction")
public abstract class AbstractXmlValidationRule implements IValidationRule<IBeansModelElement, IBeansValidationContext> {
/**
* This rule support <strong>only</strong> {@link IBeansConfig} elements as this is the model element representing a
* actual file.
*/
public final boolean supports(IModelElement element, IValidationContext context) {
return element instanceof IBeansConfig
|| (element instanceof IBeansImport && ((IBeansImport) element).getImportedBeansConfigs().size() > 0);
}
/**
* Validates a {@link IBeansConfig} or imported {@link IImportedBeansConfig} from a {@link IBeansImport}.
* <p>
* Calls for every {@link IBeansConfig} the {@link #validateBeansConfig(IBeansConfig, IBeansValidationContext)}
* method.
*/
public final void validate(IBeansModelElement element, final IBeansValidationContext context,
IProgressMonitor monitor) {
if (element instanceof IBeansConfig) {
validateBeansConfig((IBeansConfig) element, context);
}
else if (element instanceof IBeansImport) {
for (IImportedBeansConfig beansConfig : ((IBeansImport) element).getImportedBeansConfigs()) {
validateBeansConfig(beansConfig, context);
}
}
}
/**
* Validates the {@link IBeansConfig} by creating a {@link DomVisitor} and visiting the entire dom model.
* <p>
* Every element will be visited and depending on the return of {@link #supports(Node)} the
* {@link #validate(Node, IBeansValidationContext)} will be called for the node.
* @see #supports(Node)
* @see #validate(Node, IBeansValidationContext)
*/
private void validateBeansConfig(final IBeansConfig element, final IBeansValidationContext context) {
// Do not validate external configuration files
if (element.isExternal()) {
return;
}
if (!(context instanceof BeansValidationContext)) {
return;
}
IStructuredModel model = null;
try {
model = StructuredModelManager.getModelManager().getModelForRead((IFile) element.getElementResource());
if (model != null) {
Document document = ((DOMModelImpl) model).getDocument();
if (document != null && document.getDocumentElement() != null) {
DomVisitor visitor = new DomVisitor() {
private IXmlValidationContext xmlContext = new XmlValidationContext(
(BeansValidationContext) context, element);
@Override
public void validateNode(Node n) {
validate(n, xmlContext);
}
@Override
public boolean supportsNode(Node n) {
return supports(n);
}
};
visitor.visit(document);
}
}
}
catch (IOException e) {
BeansCorePlugin.log(e);
}
catch (CoreException e) {
BeansCorePlugin.log(e);
}
finally {
if (model != null) {
model.releaseFromRead();
}
}
}
/**
* Returns <code>true</code> if given {@link Node n} is supported to be validated.
* @param n the node to validate
* @return true if validation should be called
*/
protected abstract boolean supports(Node n);
/**
* Validates the given {@link Node n}
* @param n the node to validate
* @param context the current validation context
*/
protected abstract void validate(Node n, IXmlValidationContext context);
/**
* Internal visitor implementation that visits an entire {@link Document}.
*/
private abstract static class DomVisitor {
public void visit(Document dom) {
if (supportsNode(dom)) {
validateNode(dom);
}
visit(dom.getDocumentElement());
}
public void visit(Element e) {
NodeList lst = e.getChildNodes();
int len = lst.getLength();
for (int i = 0; i < len; i++) {
Node n = lst.item(i);
if (supportsNode(n)) {
validateNode(n);
}
if (n.getNodeType() == Node.ELEMENT_NODE) {
visit((Element) n);
}
}
}
public abstract void validateNode(Node n);
public abstract boolean supportsNode(Node n);
}
/**
* Internal validation context implementation that converts {@link Node} object back to
* {@link IResourceModelElement} s.
*/
private static class XmlValidationContext implements IXmlValidationContext {
private final IBeansConfig beansConfig;
private final BeansValidationContext delegateContext;
public XmlValidationContext(BeansValidationContext delegateContext, IBeansConfig beansConfig) {
this.delegateContext = delegateContext;
this.beansConfig = beansConfig;
}
/**
* {@inheritDoc}
*/
public ClassReaderFactory getClassReaderFactory() {
return delegateContext.getClassReaderFactory();
}
/**
* {@inheritDoc}
*/
public BeanDefinitionRegistry getCompleteRegistry() {
return delegateContext.getCompleteRegistry();
}
/**
* {@inheritDoc}
*/
public BeanDefinitionRegistry getIncompleteRegistry() {
return delegateContext.getIncompleteRegistry();
}
/**
* {@inheritDoc}
*/
public Set<BeanDefinition> getRegisteredBeanDefinition(String beanName, String beanClass) {
return delegateContext.getRegisteredBeanDefinition(beanName, beanClass);
}
/**
* {@inheritDoc}
*/
public IProject getRootElementProject() {
return delegateContext.getRootElementProject();
}
/**
* {@inheritDoc}
*/
public IResource getRootElementResource() {
return delegateContext.getRootElementResource();
}
/**
* {@inheritDoc}
*/
public boolean isBeanRegistered(String beanName, String beanClass) {
return delegateContext.isBeanRegistered(beanName, beanClass);
}
/**
* {@inheritDoc}
*/
public void error(IResourceModelElement element, String problemId, String message,
ValidationProblemAttribute... attributes) {
delegateContext.error(element, problemId, message, attributes);
}
/**
* {@inheritDoc}
*/
public void error(Node node, String problemId, String message, ValidationProblemAttribute... attributes) {
delegateContext.error(getResourceModelElementFromNode(node), problemId, message, attributes);
}
/**
* {@inheritDoc}
*/
public IResourceModelElement getContextElement() {
return delegateContext.getContextElement();
}
/**
* {@inheritDoc}
*/
public Set<ValidationProblem> getProblems() {
return delegateContext.getProblems();
}
/**
* {@inheritDoc}
*/
public IResourceModelElement getRootElement() {
return delegateContext.getRootElement();
}
/**
* {@inheritDoc}
*/
public void info(IResourceModelElement element, String problemId, String message,
ValidationProblemAttribute... attributes) {
delegateContext.info(element, problemId, message, attributes);
}
/**
* {@inheritDoc}
*/
public void info(Node n, String problemId, String message, ValidationProblemAttribute... attributes) {
delegateContext.info(getResourceModelElementFromNode(n), problemId, message, attributes);
}
/**
* {@inheritDoc}
*/
public void setCurrentRuleDefinition(ValidationRuleDefinition ruleDefinition) {
delegateContext.setCurrentRuleDefinition(ruleDefinition);
}
/**
* {@inheritDoc}
*/
public void warning(IResourceModelElement element, String problemId, String message,
ValidationProblemAttribute... attributes) {
delegateContext.warning(element, problemId, message, attributes);
}
/**
* {@inheritDoc}
*/
public void warning(Node n, String problemId, String message, ValidationProblemAttribute... attributes) {
delegateContext.warning(getResourceModelElementFromNode(n), problemId, message, attributes);
}
/**
* {@inheritDoc}
*/
public List<ToolAnnotationData> getToolAnnotation(Node n, String attributeName) {
return delegateContext.getToolAnnotation(n, attributeName);
}
private IResourceModelElement getResourceModelElementFromNode(Node n) {
if (n instanceof IDOMNode) {
IDOMNode domNode = ((IDOMNode) n);
int startLine = domNode.getStructuredDocument().getLineOfOffset(domNode.getStartOffset()) + 1;
int endLine = domNode.getStructuredDocument().getLineOfOffset(domNode.getStartOffset()) + 1;
IModelElement modelElement = BeansModelUtils.getMostSpecificModelElement(startLine, endLine,
(IFile) beansConfig.getElementResource(), null);
if (modelElement instanceof IResourceModelElement) {
return (IResourceModelElement) modelElement;
}
}
return delegateContext.getRootElement();
}
/**
* {@inheritDoc}
*/
public IProjectClassLoaderSupport getProjectClassLoaderSupport() {
return delegateContext.getProjectClassLoaderSupport();
}
/**
* {@inheritDoc}
*/
public void addProblems(ValidationProblem... problems) {
delegateContext.addProblems(problems);
}
}
}