/*******************************************************************************
* 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.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.aspectj.lang.reflect.PerClauseKind;
import org.eclipse.core.resources.IFile;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.asm.ClassReader;
import org.springframework.asm.Opcodes;
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.logging.AopLog;
import org.springframework.ide.eclipse.aop.core.model.IAspectDefinition;
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.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.BeansCoreUtils;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansList;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansTypedString;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.beans.core.model.IBeanProperty;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfigSet;
import org.springframework.ide.eclipse.core.java.IProjectClassLoaderSupport;
import org.springframework.ide.eclipse.core.type.asm.CachingClassReaderFactory;
import org.springframework.ide.eclipse.core.type.asm.ClassReaderFactory;
import org.springframework.util.StringUtils;
/**
* {@link IAspectDefinitionBuilder} implementation that creates {@link IAspectDefinition} from @AspectJ-style aspects.
*
* @author Christian Dupuis
* @author Martin Lippert
* @since 2.0
*/
public class AnnotationAspectDefinitionBuilder extends AbstractAspectDefinitionBuilder implements
IAspectDefinitionBuilder {
private static final String AJC_MAGIC = "ajc$";
private static final String PROXY_TARGET_CLASS = "proxyTargetClass";
private static final String INCLUDE_PATTERNS = "includePatterns";
private ClassReaderFactory classReaderFactory = null;
public void buildAspectDefinitions(List<IAspectDefinition> aspectInfos, IFile file,
IProjectClassLoaderSupport classLoaderSupport, IDocumentFactory factory) {
if (BeansCoreUtils.isBeansConfig(file, true)) {
IBeansConfig beansConfig = BeansCorePlugin.getModel().getConfig(file, true);
parseAnnotationAspects(beansConfig, aspectInfos, classLoaderSupport);
}
}
private void addAspectDefinition(IAspectDefinition info, List<IAspectDefinition> aspectInfos) {
AopLog.log(AopLog.BUILDER_MESSAGES, info.toString());
aspectInfos.add(info);
}
private void createAnnotationAspectDefinition(IBean bean, final String id, final String className,
final List<IAspectDefinition> aspectInfos) throws Throwable {
ClassReader classReader = getClassReader(className);
if (classReader == null) {
return;
}
AdviceAnnotationVisitor v = new AdviceAnnotationVisitor(id, className, bean.getElementStartLine(), bean
.getElementEndLine());
classReader.accept(v, 0);
List<IAspectDefinition> aspectDefinitions = v.getAspectDefinitions();
for (IAspectDefinition def : aspectDefinitions) {
def.setResource(bean.getElementResource());
addAspectDefinition(def, aspectInfos);
}
}
private ClassReader getClassReader(String className) {
// lazy initialize classReaderFactory to make sure it uses the correct classLoader
if (classReaderFactory == null) {
classReaderFactory = new CachingClassReaderFactory();
}
try {
return classReaderFactory.getClassReader(className);
}
catch (IOException e) {
}
return null;
}
private void parseAnnotationAspectFromSingleBean(IBeansConfig beansConfig,
IProjectClassLoaderSupport classLoaderSupport, final List<IAspectDefinition> aspectDefinitions,
AspectJAutoProxyConfiguration configuration, final IBean bean) {
final String id = bean.getElementName();
final String className = BeansModelUtils.getBeanClass(bean, beansConfig);
if (className != null && configuration.isIncluded(id)) {
try {
classLoaderSupport.executeCallback(new IProjectClassLoaderSupport.IProjectClassLoaderAwareCallback() {
public void doWithActiveProjectClassLoader() throws Throwable {
if (validateAspect(className)) {
createAnnotationAspectDefinition(bean, id, className, aspectDefinitions);
}
}
});
}
catch (Throwable e) {
AopLog.log(AopLog.BUILDER_MESSAGES, Activator.getFormattedMessage(
"AspectDefinitionBuilder.exceptionOnNode", bean));
Activator.log(e);
}
}
}
private void parseAnnotationAspects(IBeansConfig beansConfig,
List<IAspectDefinition> aspectInfos, IProjectClassLoaderSupport classLoaderSupport) {
AspectJAutoProxyConfiguration configuration = getAspectJAutoProxyConfiguration(beansConfig);
// not configured for auto proxing
if (!configuration.isAutoProxy()) {
return;
}
List<IAspectDefinition> aspectDefinitions = new ArrayList<IAspectDefinition>();
// make sure to iterate into all the beans nested inside components etc.
for (IBean bean : BeansModelUtils.getBeans(beansConfig)) {
parseAnnotationAspectFromSingleBean(beansConfig, classLoaderSupport, aspectDefinitions, configuration, bean);
}
if (configuration.isProxyTargetClass()) {
for (IAspectDefinition def : aspectDefinitions) {
((BeanAspectDefinition) def).setProxyTargetClass(configuration.isProxyTargetClass());
}
}
aspectInfos.addAll(aspectDefinitions);
}
private AspectJAutoProxyConfiguration getAspectJAutoProxyConfiguration(IBeansConfig beansConfig) {
AspectJAutoProxyConfiguration configuration = new AspectJAutoProxyConfiguration();
// Firstly check the current document for precedence of an <aop:aspectj-autoroxy> element
getAspectJConfigurationForBeansConfig(beansConfig, configuration);
// Secondly check any config sets that the given beans config is a member in
for (IBeansConfigSet configSet : BeansModelUtils.getConfigSets(beansConfig)) {
for (IBeansConfig config : configSet.getConfigs()) {
if (!config.equals(beansConfig)) {
getAspectJConfigurationForBeansConfig(beansConfig, configuration);
}
}
}
return configuration;
}
private void getAspectJConfigurationForBeansConfig(IBeansConfig beansConfig, AspectJAutoProxyConfiguration configuration) {
IBean autoproxyBean = BeansModelUtils.getBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME, beansConfig);
if (autoproxyBean != null) {
configuration.setAutoProxy(true);
IBeanProperty targetProxyClassProperty = autoproxyBean.getProperty(PROXY_TARGET_CLASS);
if (targetProxyClassProperty != null && targetProxyClassProperty.getValue() != null) {
String value = ((BeansTypedString)targetProxyClassProperty.getValue()).getString();
boolean proxyTargetClass = Boolean.valueOf(value).booleanValue();
if (proxyTargetClass) {
configuration.setProxyTargetClass(proxyTargetClass);
}
}
IBeanProperty includes = autoproxyBean.getProperty(INCLUDE_PATTERNS);
if (includes != null && includes.getValue() != null) {
List<Pattern> patterns = new ArrayList<Pattern>();
BeansList includesList = (BeansList) includes.getValue();
List<Object> includePatterns = includesList.getList();
for (Object includePattern : includePatterns) {
String pattern = ((BeansTypedString)includePattern).getString();
if (StringUtils.hasText(pattern)) {
patterns.add(Pattern.compile(pattern));
}
}
configuration.setIncluldePatterns(patterns);
}
}
}
private boolean validateAspect(String className) throws Throwable {
ClassReader classReader = getClassReader(className);
if (classReader == null) {
return false;
}
AspectAnnotationVisitor v = new AspectAnnotationVisitor();
classReader.accept(v, 0);
if (!v.getClassInfo().hasAspectAnnotation()) {
return false;
}
else {
// we know it's an aspect, but we don't know whether it is an
// @AspectJ aspect or a code style aspect.
// This is an *unclean* test whilst waiting for AspectJ to
// provide us with something better
for (String m : v.getClassInfo().getMethodNames()) {
if (m.startsWith(AJC_MAGIC)) {
// must be a code style aspect
return false;
}
}
// validate supported instantiation models
if (v.getClassInfo().getAspectAnnotation().getValue() != null) {
if (v.getClassInfo().getAspectAnnotation().getValue().toUpperCase().equals(
PerClauseKind.PERCFLOW.toString())) {
return false;
}
if (v.getClassInfo().getAspectAnnotation().getValue().toUpperCase().toString().equals(
PerClauseKind.PERCFLOWBELOW.toString())) {
return false;
}
}
// check if super class is Aspect as well and abstract
if (v.getClassInfo().getSuperType() != null) {
classReader = getClassReader(v.getClassInfo().getSuperType());
if (classReader == null) {
return false;
}
AspectAnnotationVisitor sv = new AspectAnnotationVisitor();
classReader.accept(sv, 0);
if (sv.getClassInfo().getAspectAnnotation() != null
&& !((sv.getClassInfo().getModifier() & Opcodes.ACC_ABSTRACT) != 0)) {
return false;
}
}
return true;
}
}
/**
* Merged representation of all <aop:aspectj-autoproxy /> elements in all {@link IBeansConfigSet}s
*/
class AspectJAutoProxyConfiguration {
private List<Pattern> includePatterns;
private boolean proxyTargetClass;
private boolean isAutoProxy = false;
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
public boolean isProxyTargetClass() {
return proxyTargetClass;
}
public void setAutoProxy(boolean autoProxy) {
this.isAutoProxy = autoProxy;
}
public boolean isAutoProxy() {
return isAutoProxy;
}
public void setIncluldePatterns(List<Pattern> includePatterns) {
this.includePatterns = includePatterns;
}
/**
* If no <aop:include> elements were used then includePatterns will be null and all beans are included. If
* includePatterns is non-null, then one of the patterns must match.
*/
public boolean isIncluded(String beanName) {
if (includePatterns == null) {
return true;
}
else if (includePatterns != null && includePatterns.size() == 0) {
return false;
}
else {
for (Pattern pattern : includePatterns) {
if (beanName == null) {
return false;
}
if (pattern.matcher(beanName).matches()) {
return true;
}
}
return false;
}
}
}
}