/*******************************************************************************
* Copyright (c) 2007, 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.validation.rules;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.io.Resource;
import org.springframework.ide.eclipse.beans.core.internal.model.Bean;
import org.springframework.ide.eclipse.beans.core.internal.model.BeanProperty;
import org.springframework.ide.eclipse.beans.core.internal.model.BeanReference;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.beans.core.model.IBeansList;
import org.springframework.ide.eclipse.beans.core.model.IBeansModelElement;
import org.springframework.ide.eclipse.beans.core.model.IBeansSet;
import org.springframework.ide.eclipse.beans.core.model.IBeansValueHolder;
import org.springframework.ide.eclipse.beans.core.model.validation.AbstractNonInfrastructureBeanValidationRule;
import org.springframework.ide.eclipse.beans.core.model.validation.IBeansValidationContext;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.core.model.AbstractSourceModelElement;
import org.springframework.ide.eclipse.core.model.IModelElement;
import org.springframework.ide.eclipse.core.model.IModelSourceLocation;
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.ValidationProblemAttribute;
import org.springframework.ide.eclipse.core.model.xml.XmlSourceLocation;
import org.springframework.util.StringUtils;
import org.springsource.ide.eclipse.commons.core.SpringCoreUtils;
/**
* Validates a given {@link IBean}'s or {@link IBeansValueHolder}'s bean reference(s).
* <p>
* NOTE: This {@link IValidationRule} is the only rule that works on {@link IBean} instances or its children that does
* not extend {@link AbstractNonInfrastructureBeanValidationRule}. This is on purpose as we only want to validate bean
* references for infrastructure beans.
* @author Torsten Juergeleit
* @author Christian Dupuis
* @author Terry Denney
* @since 2.0
*/
public class BeanReferenceRule implements IValidationRule<IBeansModelElement, IBeansValidationContext> {
/**
* Internal list of bean names that should be ignored by this validation rule.
*/
private List<String> ignorableBeans = new ArrayList<String>();
public void setIgnorableBeans(String beanNames) {
if (StringUtils.hasText(beanNames)) {
this.ignorableBeans = Arrays.asList(StringUtils.delimitedListToStringArray(beanNames, ",", "\r\n\f "));
}
}
/**
* Returns <code>true</code> if this rule is able to validate the given {@link IModelElement} with the specified
* {@link IValidationContext}.
* <p>
* Skip IBeansMap because it's entries (IBeansMapEntry -> IBansValueHolder) are validated instead.
*/
public boolean supports(IModelElement element, IValidationContext context) {
IBean parentBean = BeansModelUtils.getParentOfClass(element, IBean.class);
return (element instanceof Bean || element instanceof IBeansValueHolder || element instanceof IBeansList || element instanceof IBeansSet)
&& ((element instanceof IBean && !((IBean) element).isInfrastructure()) || (parentBean != null && !parentBean
.isInfrastructure()));
}
public void validate(IBeansModelElement element, IBeansValidationContext context, IProgressMonitor monitor) {
if (element instanceof Bean) {
validateBean((Bean) element, context);
}
else if (element instanceof IBeansValueHolder) {
IBeansValueHolder holder = (IBeansValueHolder) element;
validateValue(holder, holder.getValue(), context);
}
else if (element instanceof IBeansList) {
IBeansList list = (IBeansList) element;
for (Object entry : list.getList()) {
validateValue(list, entry, context);
}
}
else if (element instanceof IBeansSet) {
IBeansSet set = (IBeansSet) element;
for (Object entry : set.getSet()) {
validateValue(set, entry, context);
}
}
}
private void validateBean(Bean bean, IBeansValidationContext context) {
AbstractBeanDefinition bd = (AbstractBeanDefinition) bean.getBeanDefinition();
// Validate parent bean
if (bean.isChildBean()) {
String parentName = bean.getParentName();
if (parentName != null && !SpringCoreUtils.hasPlaceHolder(parentName)
&& !ignorableBeans.contains(parentName)) {
try {
context.getCompleteRegistry().getBeanDefinition(parentName);
}
catch (NoSuchBeanDefinitionException e) {
context.warning(bean, "UNDEFINED_PARENT_BEAN", "Parent bean '" + parentName + "' not found",
new ValidationProblemAttribute("BEAN", parentName),
new ValidationProblemAttribute("BEAN_NAME", bean.getElementName()));
}
catch (BeanDefinitionStoreException e) {
// Need to make sure that the parent of a parent does not use placeholders
Throwable exp = e;
boolean placeHolderFound = false;
while (exp != null && exp.getCause() != null) {
String msg = exp.getCause().getMessage();
if (msg.contains(SpringCoreUtils.PLACEHOLDER_PREFIX)
&& msg.contains(SpringCoreUtils.PLACEHOLDER_SUFFIX)) {
placeHolderFound = true;
break;
}
exp = exp.getCause();
}
if (!placeHolderFound) {
context.warning(bean, "UNDEFINED_PARENT_BEAN", "Parent bean '" + parentName + "' not found",
new ValidationProblemAttribute("BEAN", parentName),
new ValidationProblemAttribute("BEAN_NAME", bean.getElementName()));
}
}
}
}
// Validate depends-on beans
if (bd.getDependsOn() != null) {
for (String beanName : bd.getDependsOn()) {
validateDependsOnBean(bean, beanName, context);
}
}
}
private void validateDependsOnBean(IBean bean, String beanName, IBeansValidationContext context) {
if (beanName != null && !SpringCoreUtils.hasPlaceHolder(beanName) && !ignorableBeans.contains(beanName)) {
try {
BeanDefinition dependsBd = context.getCompleteRegistry().getBeanDefinition(beanName);
if (dependsBd.isAbstract()
|| (dependsBd.getBeanClassName() == null && dependsBd.getFactoryBeanName() == null)) {
context.error(bean, "INVALID_DEPENDS_ON_BEAN", "Referenced depends-on bean '" + beanName
+ "' is invalid (abstract or no bean class and no " + "factory bean)",
new ValidationProblemAttribute("BEAN", beanName),
new ValidationProblemAttribute("BEAN_NAME", bean.getElementName()));
}
}
catch (NoSuchBeanDefinitionException e) {
// Skip error "parent name is equal to bean name"
if (!e.getBeanName().equals(bean.getElementName())) {
context.warning(bean, "UNDEFINED_DEPENDS_ON_BEAN", "Depends-on bean '" + beanName + "' not found",
new ValidationProblemAttribute("BEAN", beanName),
new ValidationProblemAttribute("BEAN_NAME", bean.getElementName()));
}
}
}
}
private void validateValue(IResourceModelElement element, Object value, IBeansValidationContext context) {
String beanName = null;
if (value instanceof RuntimeBeanReference) {
beanName = ((RuntimeBeanReference) value).getBeanName();
}
else if (value instanceof BeanReference) {
beanName = ((BeanReference) value).getBeanName();
}
else if (value instanceof String) {
beanName = (String) value;
}
validateBeanReference(element, context, beanName);
}
private URI getLocation(IResourceModelElement element) {
if (element instanceof AbstractSourceModelElement) {
AbstractSourceModelElement sourceElement = (AbstractSourceModelElement) element;
IModelSourceLocation sourceLocation = sourceElement.getElementSourceLocation();
if (sourceLocation instanceof XmlSourceLocation) {
XmlSourceLocation xmlSourceLocation = (XmlSourceLocation) sourceLocation;
Resource resource = xmlSourceLocation.getResource();
try {
File file = resource.getFile();
if (file != null) {
return file.toURI();
}
} catch (IOException e) {
}
}
} else {
return element.getElementResource().getLocationURI();
}
return null;
}
private void validateBeanReference(IResourceModelElement element, IBeansValidationContext context, String beanName) {
// check whether element belongs in the same file as the root element, if not don't validate
// (defer validation to the file that the element belongs to)
URI elementURI = getLocation(element);
URI rootURI = getLocation(context.getRootElement());
if (elementURI != null && rootURI != null && ! elementURI.equals(rootURI)) {
return;
}
if (beanName != null && !SpringCoreUtils.hasPlaceHolder(beanName) && !ignorableBeans.contains(beanName)) {
try {
BeanDefinition refBd = context.getCompleteRegistry().getBeanDefinition(beanName);
if (refBd.isAbstract() || (refBd.getBeanClassName() == null && refBd.getFactoryBeanName() == null)) {
context.error(element, "INVALID_REFERENCED_BEAN", "Referenced bean '" + beanName + "' is invalid "
+ "(abstract or no bean class and " + "no factory bean)", new ValidationProblemAttribute(
"BEAN", beanName), new ValidationProblemAttribute("BEAN_NAME", ValidationRuleUtils.getBeanName(element)));
}
}
catch (NoSuchBeanDefinitionException e) {
// Handle factory bean references
if (ValidationRuleUtils.isFactoryBeanReference(beanName)) {
String tempBeanName = beanName.replaceFirst(ValidationRuleUtils.FACTORY_BEAN_REFERENCE_REGEXP, "");
try {
BeanDefinition def = context.getCompleteRegistry().getBeanDefinition(tempBeanName);
String beanClassName = def.getBeanClassName();
if (beanClassName != null) {
IType type = JdtUtils.getJavaType(BeansModelUtils.getProject(element).getProject(),
beanClassName);
if (type != null) {
if (!JdtUtils.doesImplement(context.getRootElementResource(), type, FactoryBean.class
.getName())) {
context.error(element, "INVALID_FACTORY_BEAN", "Referenced factory bean '"
+ tempBeanName + "' does not implement the " + "interface 'FactoryBean'",
new ValidationProblemAttribute("BEAN", tempBeanName),
new ValidationProblemAttribute("BEAN_NAME", ValidationRuleUtils.getBeanName(element)));
}
}
else {
context.warning(element, "INVALID_REFERENCED_BEAN", "Referenced factory bean '"
+ tempBeanName + "' implementation class not found",
new ValidationProblemAttribute("BEAN", tempBeanName),
new ValidationProblemAttribute("BEAN_NAME", ValidationRuleUtils.getBeanName(element)));
}
}
}
catch (NoSuchBeanDefinitionException be) {
context.warning(element, "UNDEFINED_FACTORY_BEAN", "Referenced factory bean '" + tempBeanName
+ "' not found", new ValidationProblemAttribute("BEAN", tempBeanName),
new ValidationProblemAttribute("BEAN_NAME", ValidationRuleUtils.getBeanName(element)));
}
catch (BeanDefinitionStoreException be) {
// ignore unresolvable parent bean exceptions
}
}
else {
if (element instanceof BeanProperty) {
context.warning(element, "UNDEFINED_REFERENCED_BEAN", "Referenced bean '" + beanName
+ "' not found", new ValidationProblemAttribute("BEAN", beanName),
new ValidationProblemAttribute("BEAN_NAME", ValidationRuleUtils.getBeanName(element)));
} else {
context.warning(element, "UNDEFINED_REFERENCED_BEAN", "Referenced bean '" + beanName
+ "' not found", new ValidationProblemAttribute("BEAN", beanName),
new ValidationProblemAttribute("BEAN_NAME", ValidationRuleUtils.getBeanName(element)));
}
}
}
catch (BeanDefinitionStoreException e) {
// ignore unresolvable parent bean exceptions
}
}
}
}