/*******************************************************************************
* Copyright (c) 2010 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.cdi.internal.core.validation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.jboss.tools.cdi.core.CDIConstants;
import org.jboss.tools.cdi.core.CDICorePlugin;
import org.jboss.tools.cdi.core.CDIVersion;
import org.jboss.tools.cdi.core.IClassBean;
import org.jboss.tools.cdi.core.IDecorator;
import org.jboss.tools.cdi.core.IInterceptor;
import org.jboss.tools.cdi.core.IStereotype;
import org.jboss.tools.cdi.core.preferences.CDIPreferences;
import org.jboss.tools.cdi.xml.beans.model.CDIBeansConstants;
import org.jboss.tools.common.EclipseUtil;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.impl.XModelObjectImpl;
import org.jboss.tools.common.model.util.EclipseJavaUtil;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* beans.xml validator
*
* @author Alexey Kazakov
*/
public class BeansXmlValidationDelegate extends CDICoreValidationDelegate {
private AlternativeClassValidator alternativeClassValidator;
private AlternativeStereotypeValidator alternativeStereotypeValidator;
private DecoratorTypeValidator decoratorTypeValidator;
private InterceptorTypeValidator interceptorTypeValidator;
public BeansXmlValidationDelegate(CDICoreValidator validator) {
super(validator);
}
private AlternativeClassValidator getAlternativeClassValidator() {
if(alternativeClassValidator==null) {
alternativeClassValidator = new AlternativeClassValidator();
}
return alternativeClassValidator;
}
private AlternativeStereotypeValidator getAlternativeStereotypeValidator() {
if(alternativeStereotypeValidator==null) {
alternativeStereotypeValidator = new AlternativeStereotypeValidator();
}
return alternativeStereotypeValidator;
}
private DecoratorTypeValidator getDecoratorTypeValidator() {
if(decoratorTypeValidator==null) {
decoratorTypeValidator = new DecoratorTypeValidator();
}
return decoratorTypeValidator;
}
private InterceptorTypeValidator getInterceptorTypeValidator() {
if(interceptorTypeValidator==null) {
interceptorTypeValidator = new InterceptorTypeValidator();
}
return interceptorTypeValidator;
}
public void validateBeansXml(CDICoreValidator.CDIValidationContext context, IFile beansXml) {
XModelObject f = EclipseResourceUtil.createObjectForResource(beansXml);
String xmodelpath = (f == null) ? "" : f.getPath();
IModelManager manager = StructuredModelManager.getModelManager();
if(manager == null) {
// this may happen if plug-in org.eclipse.wst.sse.core
// is stopping or un-installed, that is Eclipse is shutting down.
// there is no need to report it, just stop validation.
return;
}
IStructuredModel model = null;
try {
model = manager.getModelForRead(beansXml);
if (model instanceof IDOMModel) {
IDOMModel domModel = (IDOMModel) model;
IDOMDocument document = domModel.getDocument();
/*
* 5.1.1. Declaring selected alternatives for a bean archive
* - Each child <class> element must specify the name of an alternative bean class. If there is no class with the specified
* name, or if the class with the specified name is not an alternative bean class, the container automatically detects the problem
* and treats it as a deployment problem.
* - If the same type is listed twice under the <alternatives> element, the container automatically detects the problem and
* treats it as a deployment problem.
*/
validateTypeBeanForBeansXml(context, getAlternativeClassValidator(), document, beansXml, xmodelpath + "/" + CDIBeansConstants.NODE_ALTERNATIVES); //$NON-NLS-1$
/*
* 5.1.1. Declaring selected alternatives for a bean archive
* - Each child <stereotype> element must specify the name of an @Alternative stereotype annotation. If there is no annotation
* with the specified name, or the annotation is not an @Alternative stereotype, the container automatically detects the
* problem and treats it as a deployment problem.
* - If the same type is listed twice under the <alternatives> element, the container automatically detects the problem and
* treats it as a deployment problem.
*/
validateTypeBeanForBeansXml(context, getAlternativeStereotypeValidator(), document, beansXml, xmodelpath + "/" + CDIBeansConstants.NODE_ALTERNATIVES); //$NON-NLS-1$
/*
* 8.2. Decorator enablement and ordering
* - Each child <class> element must specify the name of a decorator bean class. If there is no class with the specified name,
* or if the class with the specified name is not a decorator bean class, the container automatically detects the problem and
* treats it as a deployment problem.
* - If the same class is listed twice under the <decorators> element, the container automatically detects the problem and
* treats it as a deployment problem.
*/
validateTypeBeanForBeansXml(context, getDecoratorTypeValidator(), document, beansXml, xmodelpath + "/" + CDIBeansConstants.NODE_DECORATORS); //$NON-NLS-1$
/*
* 9.4. Interceptor enablement and ordering
* - Each child <class> element must specify the name of an interceptor class. If there is no class with the specified name, or if
* the class with the specified name is not an interceptor class, the container automatically detects the problem and treats it as
* a deployment problem.
* - If the same class is listed twice under the <interceptors> element, the container automatically detects the problem and treats it as
* a deployment problem.
*/
validateTypeBeanForBeansXml(context, getInterceptorTypeValidator(), document, beansXml, xmodelpath + "/" + CDIBeansConstants.NODE_INTERCEPTORS); //$NON-NLS-1$
}
} catch (CoreException e) {
CDICorePlugin.getDefault().logError(e);
} catch (IOException e) {
CDICorePlugin.getDefault().logError(e);
} finally {
if (model != null) {
model.releaseFromRead();
}
}
}
private void validateTypeBeanForBeansXml(CDICoreValidator.CDIValidationContext context, TypeValidator typeValidator, IDOMDocument document, IFile beansXml, String xmodelpath) {
try {
NodeList parentNodeList = document.getElementsByTagName(typeValidator.getParrentElementname());
for (int i = 0; i < parentNodeList.getLength(); i++) {
Node parentNode = parentNodeList.item(i);
if(parentNode instanceof Element) {
List<TypeNode> typeNodes = getTypeElements((Element)parentNode, typeValidator.getTypeElementName());
Map<String, TypeNode> uniqueTypes = new HashMap<String, TypeNode>();
for (TypeNode typeNode : typeNodes) {
String typepath = xmodelpath;
String attr = null;
if(typeNode.getTypeName() != null) {
typepath = typepath + "/" + typeNode.getTypeName(); //$NON-NLS-1$
attr = typeValidator.getTypeElementName();
}
IType type = getType(context, beansXml, typeNode, typeValidator, typepath, attr);
if(type!=null) {
if(!validator.isAsYouTypeValidation() && !type.isBinary()) {
validator.getValidationContext().addLinkedCoreResource(CDICoreValidator.SHORT_ID, beansXml.getFullPath().toOSString(), type.getPath(), false);
Set<IPath> relatedResources = new HashSet<IPath>();
IResource resource = type.getResource();
if(resource instanceof IFile) {
validator.collectAllRelatedInjectionsForBean((IFile)resource, relatedResources);
for (IPath path : relatedResources) {
validator.getValidationContext().addLinkedCoreResource(CDICoreValidator.SHORT_ID, path.toOSString(), beansXml.getFullPath(), false);
}
}
}
String typeError = typeValidator.validateType(context, type);
if(typeError != null) {
IMarker marker = validator.addProblem(typeValidator.getIllegalTypeErrorMessage(getVersion(context)), CDIPreferences.ILLEGAL_TYPE_NAME_IN_BEANS_XML,
new String[]{typeNode.getTypeName()}, typeNode.getLength(), typeNode.getStartOffset(), beansXml, typeValidator.getIllegalTypeErrorMessageId());
if(marker != null) bindMarkerToModel(marker, typepath, typeValidator.getTypeElementName());
if(type.isBinary()) {
continue;
}
}
TypeNode node = uniqueTypes.get(typeNode.getTypeName());
if(node!=null) {
if(!node.isMarkedAsDuplicated()) {
IMarker marker = validator.addProblem(typeValidator.getDuplicateTypeErrorMessage(getVersion(context)), CDIPreferences.DUPLICATE_TYPE_IN_BEANS_XML,
new String[]{}, node.getLength(), node.getStartOffset(), beansXml);
if(marker != null) bindMarkerToModel(marker, typepath, typeValidator.getTypeElementName());
}
node.setMarkedAsDuplicated(true);
typeNode.setMarkedAsDuplicated(true);
typeNode.setDuplicationIndex(node.getDuplicationIndex() + 1);
IMarker marker = validator.addProblem(typeValidator.getDuplicateTypeErrorMessage(getVersion(context)), CDIPreferences.DUPLICATE_TYPE_IN_BEANS_XML,
new String[]{}, typeNode.getLength(), typeNode.getStartOffset(), beansXml);
if(marker != null) {
int di = typeNode.getDuplicationIndex();
if(di > 0) {
typepath += XModelObjectImpl.DUPLICATE + di;
}
bindMarkerToModel(marker, typepath, typeValidator.getTypeElementName());
}
}
uniqueTypes.put(typeNode.getTypeName(), typeNode);
}
}
}
}
} catch (JavaModelException e) {
CDICorePlugin.getDefault().logError(e);
}
}
private Map<IProject, IJavaProject> javaProjects;
public IJavaProject getJavaProject(IResource resource) {
if(javaProjects == null) {
javaProjects = new HashMap<IProject, IJavaProject>();
}
IProject project = resource.getProject();
if(project.isAccessible()) {
IJavaProject javaProject = javaProjects.get(project);
if(javaProject==null) {
javaProject = EclipseUtil.getJavaProject(project);
if(javaProject!=null) {
javaProjects.put(project, javaProject);
}
}
return javaProject;
}
return null;
}
private IType getType(CDICoreValidator.CDIValidationContext context, IFile beansXml, TypeNode node, TypeValidator typeValidator, String xmodelpath, String attr) {
IType type = null;
String typeName = node.getTypeName();
if(typeName!=null && typeName.trim().length() > 0) {
try {
IJavaProject javaProject = getJavaProject(beansXml);
if(javaProject!=null) {
type = EclipseJavaUtil.findType(javaProject, typeName);
}
} catch (JavaModelException e) {
CDICorePlugin.getDefault().logError(e);
return null;
}
} else {
IMarker marker = validator.addProblem(typeValidator.getEmptyTypeErrorMessage(getVersion(context)), CDIPreferences.ILLEGAL_TYPE_NAME_IN_BEANS_XML,
new String[]{node.getTypeName()}, node.getLength(), node.getStartOffset(), beansXml, typeValidator.getUnknownTypeErrorMessageId());
bindMarkerToModel(marker, xmodelpath, attr);
return null;
}
if(type==null) {
addLinkedResourcesForUnknownType(beansXml, node.getTypeName());
IMarker marker = validator.addProblem(typeValidator.getUnknownTypeErrorMessage(getVersion(context)), CDIPreferences.ILLEGAL_TYPE_NAME_IN_BEANS_XML,
new String[]{node.getTypeName()}, node.getLength(), node.getStartOffset(), beansXml, typeValidator.getUnknownTypeErrorMessageId());
bindMarkerToModel(marker, xmodelpath, attr);
}
return type;
}
private void bindMarkerToModel(IMarker marker, String path, String attribute) {
try {
if(marker!=null) {
marker.setAttribute("path", path); //$NON-NLS-1$
if(attribute != null) {
marker.setAttribute("attribute", attribute); //$NON-NLS-1$
}
}
} catch(CoreException e) {
CDICorePlugin.getDefault().logError(e);
}
}
private void addLinkedResourcesForUnknownType(IFile beansXml, String typeName) {
if(!validator.isAsYouTypeValidation() && typeName!=null && typeName.trim().length()>0) {
IStatus status = JavaConventions.validateJavaTypeName(typeName, CompilerOptions.VERSION_1_7, CompilerOptions.VERSION_1_7);
if(status.getSeverity()!=IStatus.ERROR) {
String packagePath = typeName.replace('.', '/');
Set<IFolder> sources = EclipseResourceUtil.getSourceFolders(beansXml.getProject());
for (IFolder source : sources) {
IPath path = source.getFullPath().append(packagePath + ".java"); //$NON-NLS-1$
validator.getValidationContext().addLinkedCoreResource(CDICoreValidator.SHORT_ID, beansXml.getFullPath().toOSString(), path, false);
}
}
}
}
private List<TypeNode> getTypeElements(Element parentElement, String typeElementName) {
List<TypeNode> result = new ArrayList<TypeNode>();
NodeList list = parentElement.getElementsByTagName(typeElementName);
for (int i = 0; i < list.getLength(); i++) {
Node classNode = list.item(i);
NodeList children = classNode.getChildNodes();
boolean empty = true;
for (int j = 0; j < children.getLength(); j++) {
Node node = children.item(j);
if(node.getNodeType() == Node.TEXT_NODE) {
String value = node.getNodeValue();
if(value!=null) {
String className = value.trim();
if(className.length()==0) {
continue;
}
empty = false;
if(node instanceof IndexedRegion) {
int start = ((IndexedRegion)node).getStartOffset() + value.indexOf(className);
int length = className.length();
result.add(new TypeNode(start, length, className));
break;
}
}
}
}
if(empty && classNode instanceof IndexedRegion) {
int start = ((IndexedRegion)classNode).getStartOffset();
int end = ((IndexedRegion)classNode).getEndOffset();
int length = end - start;
result.add(new TypeNode(start, length, null));
}
}
return result;
}
private static class TypeNode {
private int startOffset;
private int length;
private String typeName;
private boolean markedAsDuplicated;
private int duplicationIndex = 0;
public TypeNode(int startOffset, int length, String typeName) {
this.startOffset = startOffset;
this.length = length;
this.typeName = typeName;
}
public int getStartOffset() {
return startOffset;
}
public void setStartOffset(int startOffset) {
this.startOffset = startOffset;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
/**
* Returns type name or null if value in XML is empty or whitespace.
* @return
*/
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public boolean isMarkedAsDuplicated() {
return markedAsDuplicated;
}
public void setMarkedAsDuplicated(boolean markedAsDuplicated) {
this.markedAsDuplicated = markedAsDuplicated;
}
public int getDuplicationIndex() {
return duplicationIndex;
}
public void setDuplicationIndex(int i) {
duplicationIndex = i;
}
}
protected CDIVersion getVersion(CDICoreValidator.CDIValidationContext context) {
return context.getCdiProject().getVersion();
}
private static interface TypeValidator {
String validateType(CDICoreValidator.CDIValidationContext context, IType type) throws JavaModelException;
String getTypeElementName();
String getParrentElementname();
String getEmptyTypeErrorMessage(CDIVersion version);
String getUnknownTypeErrorMessage(CDIVersion version);
int getUnknownTypeErrorMessageId();
String getIllegalTypeErrorMessage(CDIVersion version);
int getIllegalTypeErrorMessageId();
String getDuplicateTypeErrorMessage(CDIVersion version);
}
private abstract class AbstractTypeValidator implements TypeValidator {
@Override
public String getTypeElementName() {
return "class"; //$NON-NLS-1$
}
@Override
public String validateType(CDICoreValidator.CDIValidationContext context, IType type) throws JavaModelException {
if(!validateKindOfType(type)) {
return getIllegalTypeErrorMessage(getVersion(context));
}
if(type.isBinary()) {
if(!validateBinaryType(type)) {
return getIllegalTypeErrorMessage(getVersion(context));
}
} else if(!validateSourceType(context, type)) {
return getIllegalTypeErrorMessage(getVersion(context));
}
return null;
}
abstract public boolean validateSourceType(CDICoreValidator.CDIValidationContext context, IType type);
/**
* Validates if the type represens class/annotation/...
* @param type
* @return
* @throws JavaModelException
*/
public boolean validateKindOfType(IType type) throws JavaModelException {
return type.isClass();
}
public boolean validateBinaryType(IType type) throws JavaModelException {
IAnnotation[] annotations = type.getAnnotations();
for (IAnnotation annotation : annotations) {
if(annotation.getElementName().equals(getAnnotationName())) {
return true;
}
}
return false;
}
protected abstract String getAnnotationName();
}
private class AlternativeClassValidator extends AbstractTypeValidator {
@Override
public boolean validateSourceType(CDICoreValidator.CDIValidationContext context, IType type) {
IClassBean classBean = context.getCdiProject().getBeanClass(type);
return classBean!=null && classBean.isAlternative();
}
@Override
public String getParrentElementname() {
return "alternatives"; //$NON-NLS-1$
}
@Override
public String getEmptyTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.EMPTY_ALTERNATIVE_BEAN_CLASS_NAME[version.getIndex()];
}
@Override
public String getUnknownTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.UNKNOWN_ALTERNATIVE_BEAN_CLASS_NAME[version.getIndex()];
}
@Override
public int getUnknownTypeErrorMessageId() {
return CDIValidationErrorManager.UNKNOWN_ALTERNATIVE_BEAN_CLASS_NAME_ID;
}
@Override
public String getIllegalTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.ILLEGAL_ALTERNATIVE_BEAN_CLASS[version.getIndex()];
}
@Override
public int getIllegalTypeErrorMessageId() {
return CDIValidationErrorManager.ILLEGAL_ALTERNATIVE_BEAN_CLASS_ID;
}
@Override
public String getDuplicateTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.DUPLICATE_ALTERNATIVE_TYPE[version.getIndex()];
}
@Override
protected String getAnnotationName() {
return CDIConstants.ALTERNATIVE_ANNOTATION_TYPE_NAME;
}
}
private class AlternativeStereotypeValidator extends AbstractTypeValidator {
@Override
public boolean validateSourceType(CDICoreValidator.CDIValidationContext context, IType type) {
IStereotype stereotype = context.getCdiProject().getStereotype(type);
return stereotype!=null && stereotype.isAlternative();
}
@Override
public boolean validateKindOfType(IType type) throws JavaModelException {
return type.isAnnotation();
}
@Override
public String getTypeElementName() {
return "stereotype"; //$NON-NLS-1$
}
@Override
public String getParrentElementname() {
return "alternatives"; //$NON-NLS-1$
}
@Override
public String getEmptyTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.EMPTY_ALTERNATIVE_ANNOTATION_NAME[version.getIndex()];
}
@Override
public String getUnknownTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.UNKNOWN_ALTERNATIVE_ANNOTATION_NAME[version.getIndex()];
}
@Override
public int getUnknownTypeErrorMessageId() {
return CDIValidationErrorManager.UNKNOWN_ALTERNATIVE_ANNOTATION_NAME_ID;
}
@Override
public String getIllegalTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.ILLEGAL_ALTERNATIVE_ANNOTATION[version.getIndex()];
}
@Override
public int getIllegalTypeErrorMessageId() {
return CDIValidationErrorManager.ILLEGAL_ALTERNATIVE_ANNOTATION_ID;
}
@Override
public String getDuplicateTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.DUPLICATE_ALTERNATIVE_TYPE[version.getIndex()];
}
@Override
protected String getAnnotationName() {
return CDIConstants.ALTERNATIVE_ANNOTATION_TYPE_NAME;
}
}
private class DecoratorTypeValidator extends AbstractTypeValidator {
@Override
public boolean validateSourceType(CDICoreValidator.CDIValidationContext context, IType type) {
IClassBean classBean = context.getCdiProject().getBeanClass(type);
return classBean instanceof IDecorator;
}
@Override
public String getParrentElementname() {
return "decorators"; //$NON-NLS-1$
}
@Override
public String getEmptyTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.EMPTY_DECORATOR_BEAN_CLASS_NAME[version.getIndex()];
}
@Override
public String getUnknownTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.UNKNOWN_DECORATOR_BEAN_CLASS_NAME[version.getIndex()];
}
@Override
public int getUnknownTypeErrorMessageId() {
return CDIValidationErrorManager.UNKNOWN_DECORATOR_BEAN_CLASS_NAME_ID;
}
@Override
public String getIllegalTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.ILLEGAL_DECORATOR_BEAN_CLASS[version.getIndex()];
}
@Override
public int getIllegalTypeErrorMessageId() {
return CDIValidationErrorManager.ILLEGAL_DECORATOR_BEAN_CLASS_ID;
}
@Override
public String getDuplicateTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.DUPLICATE_DECORATOR_CLASS[version.getIndex()];
}
@Override
protected String getAnnotationName() {
return CDIConstants.DECORATOR_STEREOTYPE_TYPE_NAME;
}
}
private class InterceptorTypeValidator extends AbstractTypeValidator {
@Override
public boolean validateSourceType(CDICoreValidator.CDIValidationContext context, IType type) {
IClassBean classBean = context.getCdiProject().getBeanClass(type);
return classBean instanceof IInterceptor;
}
@Override
public String getParrentElementname() {
return "interceptors"; //$NON-NLS-1$
}
@Override
public String getEmptyTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.EMPTY_INTERCEPTOR_CLASS_NAME[version.getIndex()];
}
@Override
public String getUnknownTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.UNKNOWN_INTERCEPTOR_CLASS_NAME[version.getIndex()];
}
@Override
public int getUnknownTypeErrorMessageId() {
return CDIValidationErrorManager.UNKNOWN_INTERCEPTOR_CLASS_NAME_ID;
}
@Override
public String getIllegalTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.ILLEGAL_INTERCEPTOR_CLASS[version.getIndex()];
}
@Override
public int getIllegalTypeErrorMessageId() {
return CDIValidationErrorManager.ILLEGAL_INTERCEPTOR_CLASS_ID;
}
@Override
public String getDuplicateTypeErrorMessage(CDIVersion version) {
return CDIValidationMessages.DUPLICATE_INTERCEPTOR_CLASS[version.getIndex()];
}
@Override
protected String getAnnotationName() {
return CDIConstants.INTERCEPTOR_ANNOTATION_TYPE_NAME;
}
}
}