/*******************************************************************************
* Copyright (c) 2007, 2013 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.aop.core.internal.model.builder;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.eclipse.core.resources.IFile;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.ide.eclipse.aop.core.Activator;
import org.springframework.ide.eclipse.aop.core.internal.model.BeanAspectDefinition;
import org.springframework.ide.eclipse.aop.core.internal.model.BeanIntroductionDefinition;
import org.springframework.ide.eclipse.aop.core.internal.model.JavaAdvisorDefinition;
import org.springframework.ide.eclipse.aop.core.logging.AopLog;
import org.springframework.ide.eclipse.aop.core.model.IAopReference;
import org.springframework.ide.eclipse.aop.core.model.IAspectDefinition;
import org.springframework.ide.eclipse.aop.core.model.IAopReference.ADVICE_TYPE;
import org.springframework.ide.eclipse.aop.core.model.builder.IAspectDefinitionBuilder;
import org.springframework.ide.eclipse.aop.core.model.builder.IDocumentFactory;
import org.springframework.ide.eclipse.beans.ui.editor.util.BeansEditorUtils;
import org.springframework.ide.eclipse.core.java.ClassUtils;
import org.springframework.ide.eclipse.core.java.IProjectClassLoaderSupport;
import org.springframework.util.StringUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Builder implementation that creates {@link IAspectDefinition} from Spring xml definition files.
* Understands aop:config tags.
*
* @author Christian Dupuis
* @author Martin Lippert
* @since 2.0
*/
@SuppressWarnings("restriction")
public class XmlAspectDefinitionBuilder extends AbstractAspectDefinitionBuilder implements
IAspectDefinitionBuilder {
private static final String ADVICE_REF_ATTRIBUTE = "advice-ref";
private static final String ADVISOR_ELEMENT = "advisor";
private static final String AFTER_ELEMENT = "after";
private static final String AFTER_RETURNING_ELEMENT = "after-returning";
private static final String AFTER_THROWING_ELEMENT = "after-throwing";
private static final String AOP_NAMESPACE_URI = "http://www.springframework.org/schema/aop";
private static final String ARG_NAMES_ATTRIBUTE = "arg-names";
private static final String AROUND_ELEMENT = "around";
private static final String ASPECT_ELEMENT = "aspect";
private static final String BEFORE_ELEMENT = "before";
private static final String CONFIG_ELEMENT = "config";
private static final String DECLARE_PARENTS_ELEMENT = "declare-parents";
private static final String DEFAULT_IMPL_ATTRIBUTE = "default-impl";
private static final String DELEGATE_REF_ATTRIBUTE = "delegate-ref";
private static final String EXPRESSION_ATTRIBUTE = "expression";
private static final String ID_ATTRIBUTE = "id";
private static final String IMPLEMENT_INTERFACE_ATTRIBUTE = "implement-interface";
private static final String METHOD_ATTRIBUTE = "method";
private static final String POINTCUT_ELEMENT = "pointcut";
private static final String POINTCUT_REF_ATTRIBUTE = "pointcut-ref";
private static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class";
private static final String RETURNING_ATTRIBUTE = "returning";
private static final String THROWING_ATTRIBUTE = "throwing";
private static final String TYPES_MATCHING_ATTRIBUTE = "types-matching";
public void buildAspectDefinitions(List<IAspectDefinition> aspectInfos, IFile file,
IProjectClassLoaderSupport classLoaderSupport, IDocumentFactory factory) {
if (file.getFileExtension() != null && file.getFileExtension().equals("xml")) {
parseAopConfigElement(factory.createDocument(file), file, aspectInfos, classLoaderSupport);
}
}
private void addAspectDefinition(IAspectDefinition info, List<IAspectDefinition> aspectInfos) {
AopLog.log(AopLog.BUILDER_MESSAGES, info.toString());
aspectInfos.add(info);
}
private String getPointcut(final Node aspectNode, Map<String, String> rootPointcuts,
Map<String, String> pointcuts) {
final String pointcut;
String pointcutAttribute = getAttribute(aspectNode, POINTCUT_ELEMENT);
String pointcutRef = getAttribute(aspectNode, POINTCUT_REF_ATTRIBUTE);
if (!StringUtils.hasText(pointcutAttribute)) {
if (pointcuts.containsKey(pointcutRef)) {
pointcut = pointcuts.get(pointcutRef);
}
else {
pointcut = rootPointcuts.get(pointcutRef);
}
}
else {
pointcut = pointcutAttribute;
}
return pointcut;
}
private void parseAdvisorElement(final IFile file, final Node aspectNode,
Map<String, String> rootPointcuts, final List<IAspectDefinition> aspectInfos,
IProjectClassLoaderSupport classLoaderSupport) {
final String beanRef = getAttribute(aspectNode, ADVICE_REF_ATTRIBUTE);
final String className = BeansEditorUtils.getClassNameForBean(file, aspectNode
.getOwnerDocument(), beanRef);
if (StringUtils.hasText(className)) {
NodeList aspectChildren = aspectNode.getParentNode().getChildNodes();
Map<String, String> pointcuts = new HashMap<String, String>();
parsePointcuts(pointcuts, aspectChildren);
final String pointcutExpression = getPointcut(aspectNode, rootPointcuts, pointcuts);
try {
classLoaderSupport
.executeCallback(new IProjectClassLoaderSupport.IProjectClassLoaderAwareCallback() {
public void doWithActiveProjectClassLoader() throws Throwable {
Class<?> advisorClass = ClassUtils.loadClass(className);
if (ClassUtils.loadClass(MethodInterceptor.class).isAssignableFrom(
advisorClass)) {
JavaAdvisorDefinition info = prepareJavaAdvisorDefinition(file,
aspectNode, beanRef, className, pointcutExpression);
info.setType(ADVICE_TYPE.AROUND);
info.setAdviceMethodName("invoke");
info.setAdviceMethodParameterTypes(new String[] { MethodInvocation.class
.getName() });
addAspectDefinition(info, aspectInfos);
}
if (ClassUtils.loadClass(MethodBeforeAdvice.class)
.isAssignableFrom(advisorClass)) {
JavaAdvisorDefinition info = prepareJavaAdvisorDefinition(file,
aspectNode, beanRef, className, pointcutExpression);
info.setType(ADVICE_TYPE.BEFORE);
info.setAdviceMethodName(BEFORE_ELEMENT);
info.setAdviceMethodParameterTypes(new String[] {
Method.class.getName(), Object[].class.getName(),
Object.class.getName() });
addAspectDefinition(info, aspectInfos);
}
if (ClassUtils.loadClass(ThrowsAdvice.class).isAssignableFrom(
advisorClass)) {
JavaAdvisorDefinition info = prepareJavaAdvisorDefinition(file,
aspectNode, beanRef, className, pointcutExpression);
info.setType(ADVICE_TYPE.AFTER_THROWING);
info.setAdviceMethodName("afterThrowing");
info.setAdviceMethodParameterTypes(new String[] {
Method.class.getName(), Object[].class.getName(),
Object.class.getName(), Exception.class.getName() });
addAspectDefinition(info, aspectInfos);
}
if (ClassUtils.loadClass(AfterReturningAdvice.class)
.isAssignableFrom(advisorClass)) {
JavaAdvisorDefinition info = prepareJavaAdvisorDefinition(file,
aspectNode, beanRef, className, pointcutExpression);
info.setType(ADVICE_TYPE.AFTER_RETURNING);
info.setAdviceMethodName("afterReturning");
info.setAdviceMethodParameterTypes(new String[] {
Object.class.getName(), Method.class.getName(),
Object[].class.getName(), Object.class.getName() });
addAspectDefinition(info, aspectInfos);
}
}
});
}
catch (Throwable e) {
AopLog.log(AopLog.BUILDER_MESSAGES, Activator.getFormattedMessage(
"AspectDefinitionBuilder.exceptionOnAdvisorNode", aspectNode));
// Activator.log(e);
}
}
}
private void parseAopConfigElement(final IDOMDocument document, IFile file,
final List<IAspectDefinition> aspectInfos, IProjectClassLoaderSupport classLoaderSupport) {
if (document == null || document.getStructuredDocument() == null) {
return;
}
NodeList list = document.getDocumentElement().getElementsByTagNameNS(AOP_NAMESPACE_URI,
CONFIG_ELEMENT);
for (int i = 0; i < list.getLength(); i++) {
List<IAspectDefinition> aspectDefinitions = new ArrayList<IAspectDefinition>();
Map<String, String> rootPointcuts = new HashMap<String, String>();
Node node = list.item(i);
NodeList children = node.getChildNodes();
parsePointcuts(rootPointcuts, children);
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (ASPECT_ELEMENT.equals(child.getLocalName())) {
parseAspectElement(file, child, rootPointcuts, aspectDefinitions);
}
else if (ADVISOR_ELEMENT.equals(child.getLocalName())) {
parseAdvisorElement(file, child, rootPointcuts, aspectDefinitions,
classLoaderSupport);
}
}
if (node.getAttributes().getNamedItem(PROXY_TARGET_CLASS_ATTRIBUTE) != null) {
boolean proxyTargetClass = Boolean.valueOf(node.getAttributes().getNamedItem(
PROXY_TARGET_CLASS_ATTRIBUTE).getNodeValue());
if (proxyTargetClass) {
for (IAspectDefinition def : aspectDefinitions) {
((BeanAspectDefinition) def).setProxyTargetClass(proxyTargetClass);
}
}
}
aspectInfos.addAll(aspectDefinitions);
}
}
private void parseAspectElement(IFile file, Node child, Map<String, String> rootPointcuts,
List<IAspectDefinition> aspectInfos) {
String beanRef = getAttribute(child, "ref");
String className = BeansEditorUtils.getClassNameForBean(file, child.getOwnerDocument(),
beanRef);
NodeList aspectChildren = child.getChildNodes();
Map<String, String> pointcuts = new HashMap<String, String>();
parsePointcuts(pointcuts, aspectChildren);
for (int g = 0; g < aspectChildren.getLength(); g++) {
Node aspectNode = aspectChildren.item(g);
BeanAspectDefinition info = null;
if (DECLARE_PARENTS_ELEMENT.equals(aspectNode.getLocalName())) {
String typesMatching = getAttribute(aspectNode, TYPES_MATCHING_ATTRIBUTE);
String defaultImpl = getAttribute(aspectNode, DEFAULT_IMPL_ATTRIBUTE);
String implementInterface = getAttribute(aspectNode, IMPLEMENT_INTERFACE_ATTRIBUTE);
String delegateRef = getAttribute(aspectNode, DELEGATE_REF_ATTRIBUTE);
if (StringUtils.hasText(typesMatching)
&& (StringUtils.hasText(defaultImpl) || StringUtils.hasText(delegateRef))
&& StringUtils.hasText(implementInterface)) {
info = new BeanIntroductionDefinition();
((BeanIntroductionDefinition) info)
.setIntroducedInterfaceName(implementInterface);
((BeanIntroductionDefinition) info).setTypePattern(typesMatching);
if (StringUtils.hasText(delegateRef)) {
Node delegateBean = BeansEditorUtils.getFirstReferenceableNodeById(
aspectNode.getOwnerDocument(), delegateRef, file);
if (delegateBean != null) {
defaultImpl = BeansEditorUtils.getClassNameForBean(delegateBean);
}
}
((BeanIntroductionDefinition) info).setDefaultImplName(defaultImpl);
((BeanIntroductionDefinition) info).setAspectClassName(defaultImpl);
((BeanIntroductionDefinition) info).setAspectName(beanRef);
extractLineNumbers(info, (IDOMNode) aspectNode);
}
}
else if (StringUtils.hasText(className)) {
if (BEFORE_ELEMENT.equals(aspectNode.getLocalName())) {
info = repareBeanAspectDefinition(pointcuts, rootPointcuts, aspectNode,
IAopReference.ADVICE_TYPE.BEFORE);
}
else if (AROUND_ELEMENT.equals(aspectNode.getLocalName())) {
info = repareBeanAspectDefinition(pointcuts, rootPointcuts, aspectNode,
IAopReference.ADVICE_TYPE.AROUND);
}
else if (AFTER_ELEMENT.equals(aspectNode.getLocalName())) {
info = repareBeanAspectDefinition(pointcuts, rootPointcuts, aspectNode,
IAopReference.ADVICE_TYPE.AFTER);
}
else if (AFTER_RETURNING_ELEMENT.equals(aspectNode.getLocalName())) {
info = repareBeanAspectDefinition(pointcuts, rootPointcuts, aspectNode,
IAopReference.ADVICE_TYPE.AFTER_RETURNING);
String returning = getAttribute(aspectNode, RETURNING_ATTRIBUTE);
info.setReturning(returning);
}
else if (AFTER_THROWING_ELEMENT.equals(aspectNode.getLocalName())) {
info = repareBeanAspectDefinition(pointcuts, rootPointcuts, aspectNode,
IAopReference.ADVICE_TYPE.AFTER_THROWING);
String throwing = getAttribute(aspectNode, THROWING_ATTRIBUTE);
info.setThrowing(throwing);
}
else if (AROUND_ELEMENT.equals(aspectNode.getLocalName())) {
info = repareBeanAspectDefinition(pointcuts, rootPointcuts, aspectNode,
IAopReference.ADVICE_TYPE.AROUND);
}
}
if (info != null) {
if (info.getAspectClassName() == null) {
info.setAspectClassName(className);
}
info.setAspectName(beanRef);
info.setResource(file);
addAspectDefinition(info, aspectInfos);
}
}
}
private void parsePointcuts(Map<String, String> rootPointcuts, NodeList children) {
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (POINTCUT_ELEMENT.equals(child.getLocalName())) {
String id = getAttribute(child, ID_ATTRIBUTE);
String expression = getAttribute(child, EXPRESSION_ATTRIBUTE);
if (StringUtils.hasText(id) && StringUtils.hasText(expression)) {
rootPointcuts.put(id, expression);
}
}
}
}
private JavaAdvisorDefinition prepareJavaAdvisorDefinition(final IFile file,
final Node aspectNode, final String beanRef, final String className,
final String pointcutExpression) {
JavaAdvisorDefinition info = new JavaAdvisorDefinition();
extractLineNumbers(info, (IDOMNode) aspectNode);
info.setPointcutExpression(pointcutExpression);
info.setAspectClassName(className);
info.setAspectName(beanRef);
info.setResource(file);
return info;
}
private BeanAspectDefinition repareBeanAspectDefinition(Map<String, String> pointcuts,
Map<String, String> rootPointcuts, Node aspectNode, IAopReference.ADVICE_TYPE type) {
BeanAspectDefinition info = new BeanAspectDefinition();
String pointcut = getAttribute(aspectNode, POINTCUT_ELEMENT);
String pointcutRef = getAttribute(aspectNode, POINTCUT_REF_ATTRIBUTE);
if (!StringUtils.hasText(pointcut)) {
pointcut = pointcuts.get(pointcutRef);
if (!StringUtils.hasText(pointcut)) {
pointcut = rootPointcuts.get(pointcutRef);
}
}
String argNames = getAttribute(aspectNode, ARG_NAMES_ATTRIBUTE);
String method = getAttribute(aspectNode, METHOD_ATTRIBUTE);
String[] argNamesArray = null;
if (argNames != null) {
argNamesArray = StringUtils.commaDelimitedListToStringArray(argNames);
}
info.setArgNames(argNamesArray);
extractLineNumbers(info, (IDOMNode) aspectNode);
info.setPointcutExpression(pointcut);
info.setType(type);
info.setAdviceMethodName(method);
return info;
}
}