/*******************************************************************************
* Copyright (c) 2005 Contributors.
* 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://eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexandre Vasseur initial implementation
* David Knibb weaving context enhancments
*******************************************************************************/
package org.aspectj.weaver.loadtime;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import org.aspectj.asm.IRelationship;
import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.util.LangUtil;
import org.aspectj.weaver.ICrossReferenceHandler;
import org.aspectj.weaver.Lint;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.Lint.Kind;
import org.aspectj.weaver.bcel.BcelWeaver;
import org.aspectj.weaver.loadtime.definition.Definition;
import org.aspectj.weaver.loadtime.definition.DocumentParser;
import org.aspectj.weaver.ltw.LTWWorld;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.TypePattern;
import org.aspectj.weaver.tools.GeneratedClassHandler;
import org.aspectj.weaver.tools.Trace;
import org.aspectj.weaver.tools.TraceFactory;
import org.aspectj.weaver.tools.WeavingAdaptor;
/**
* @author Alexandre Vasseur
*/
public class ClassLoaderWeavingAdaptor extends WeavingAdaptor {
private final static String AOP_XML = "META-INF/aop.xml";
private boolean initialized;
private List m_dumpTypePattern = new ArrayList();
private boolean m_dumpBefore = false;
private List m_includeTypePattern = new ArrayList();
private List m_excludeTypePattern = new ArrayList();
private List m_includeStartsWith = new ArrayList();
private List m_excludeStartsWith = new ArrayList();
private List m_aspectExcludeTypePattern = new ArrayList();
private List m_aspectExcludeStartsWith = new ArrayList();
private List m_aspectIncludeTypePattern = new ArrayList();
private List m_aspectIncludeStartsWith = new ArrayList();
private StringBuffer namespace;
private IWeavingContext weavingContext;
private static Trace trace = TraceFactory.getTraceFactory().getTrace(ClassLoaderWeavingAdaptor.class);
public ClassLoaderWeavingAdaptor() {
super();
if (trace.isTraceEnabled()) trace.enter("",this);
if (trace.isTraceEnabled()) trace.exit("");
}
/**
* We don't need a reference to the class loader and using it during
* construction can cause problems with recursion. It also makes sense
* to supply the weaving context during initialization to.
* @deprecated
*/
public ClassLoaderWeavingAdaptor(final ClassLoader deprecatedLoader, final IWeavingContext deprecatedContext) {
super();
if (trace.isTraceEnabled()) trace.enter("",this,new Object[] { deprecatedLoader, deprecatedContext });
if (trace.isTraceEnabled()) trace.exit("");
}
protected void initialize (final ClassLoader classLoader, IWeavingContext context) {
//super(null);// at this stage we don't have yet a generatedClassHandler to define to the VM the closures
if (initialized) return;
if (trace.isTraceEnabled()) trace.enter("initialize",this,new Object[] { classLoader, context });
this.weavingContext = context;
if (weavingContext == null) {
weavingContext = new DefaultWeavingContext(classLoader);
}
createMessageHandler();
this.generatedClassHandler = new GeneratedClassHandler() {
/**
* Callback when we need to define a Closure in the JVM
*
* @param name
* @param bytes
*/
public void acceptClass(String name, byte[] bytes) {
try {
if (shouldDump(name.replace('/', '.'), false)) {
dump(name, bytes, false);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
defineClass(classLoader, name, bytes);// could be done lazily using the hook
}
};
List definitions = parseDefinitions(classLoader);
if (!isEnabled()) {
if (trace.isTraceEnabled()) trace.exit("initialize",false);
return;
}
bcelWorld = new LTWWorld(
classLoader, weavingContext, // TODO when the world works in terms of the context, we can remove the loader...
getMessageHandler(), new ICrossReferenceHandler() {
public void addCrossReference(ISourceLocation from, ISourceLocation to, IRelationship.Kind kind, boolean runtimeTest) {
;// for tools only
}
}
);
// //TODO this AJ code will call
// //org.aspectj.apache.bcel.Repository.setRepository(this);
// //ie set some static things
// //==> bogus as Bcel is expected to be
// org.aspectj.apache.bcel.Repository.setRepository(new ClassLoaderRepository(loader));
weaver = new BcelWeaver(bcelWorld);
// register the definitions
registerDefinitions(weaver, classLoader, definitions);
if (isEnabled()) {
//bcelWorld.setResolutionLoader(loader.getParent());//(ClassLoader)null);//
// after adding aspects
weaver.prepareForWeave();
}
else {
bcelWorld = null;
weaver = null;
}
initialized = true;
if (trace.isTraceEnabled()) trace.exit("initialize",isEnabled());
}
/**
* Load and cache the aop.xml/properties according to the classloader visibility rules
*
* @param weaver
* @param loader
*/
private List parseDefinitions(final ClassLoader loader) {
List definitions = new ArrayList();
try {
info("register classloader " + getClassLoaderName(loader));
//TODO av underoptimized: we will parse each XML once per CL that see it
//TODO av dev mode needed ? TBD -Daj5.def=...
if (ClassLoader.getSystemClassLoader().equals(loader)) {
String file = System.getProperty("aj5.def", null);
if (file != null) {
info("using (-Daj5.def) " + file);
definitions.add(DocumentParser.parse((new File(file)).toURL()));
}
}
String resourcePath = System.getProperty("org.aspectj.weaver.loadtime.configuration",AOP_XML);
StringTokenizer st = new StringTokenizer(resourcePath,";");
while(st.hasMoreTokens()){
Enumeration xmls = weavingContext.getResources(st.nextToken());
// System.out.println("? registerDefinitions: found-aop.xml=" + xmls.hasMoreElements() + ", loader=" + loader);
while (xmls.hasMoreElements()) {
URL xml = (URL) xmls.nextElement();
info("using configuration " + weavingContext.getFile(xml));
definitions.add(DocumentParser.parse(xml));
}
}
if (definitions.isEmpty()) {
disable();// will allow very fast skip in shouldWeave()
info("no configuration found. Disabling weaver for class loader " + getClassLoaderName(loader));
}
} catch (Exception e) {
disable();// will allow very fast skip in shouldWeave()
warn("parse definitions failed",e);
}
return definitions;
}
private void registerDefinitions(final BcelWeaver weaver, final ClassLoader loader, List definitions) {
try {
registerOptions(weaver, loader, definitions);
registerAspectExclude(weaver, loader, definitions);
registerAspectInclude(weaver, loader, definitions);
registerAspects(weaver, loader, definitions);
registerIncludeExclude(weaver, loader, definitions);
registerDump(weaver, loader, definitions);
} catch (Exception e) {
disable();// will allow very fast skip in shouldWeave()
warn("register definition failed",(e instanceof AbortException)?null:e);
}
}
private String getClassLoaderName (ClassLoader loader) {
return weavingContext.getClassLoaderName();
}
/**
* Configure the weaver according to the option directives
* TODO av - don't know if it is that good to reuse, since we only allow a small subset of options in LTW
*
* @param weaver
* @param loader
* @param definitions
*/
private void registerOptions(final BcelWeaver weaver, final ClassLoader loader, final List definitions) {
StringBuffer allOptions = new StringBuffer();
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
allOptions.append(definition.getWeaverOptions()).append(' ');
}
Options.WeaverOption weaverOption = Options.parse(allOptions.toString(), loader, getMessageHandler());
// configure the weaver and world
// AV - code duplicates AspectJBuilder.initWorldAndWeaver()
World world = weaver.getWorld();
setMessageHandler(weaverOption.messageHandler);
world.setXlazyTjp(weaverOption.lazyTjp);
world.setXHasMemberSupportEnabled(weaverOption.hasMember);
world.setOptionalJoinpoints(weaverOption.optionalJoinpoints);
world.setPinpointMode(weaverOption.pinpoint);
weaver.setReweavableMode(weaverOption.notReWeavable);
world.performExtraConfiguration(weaverOption.xSet);
world.setXnoInline(weaverOption.noInline);
// AMC - autodetect as per line below, needed for AtAjLTWTests.testLTWUnweavable
world.setBehaveInJava5Way(LangUtil.is15VMOrGreater());
world.setAddSerialVerUID(weaverOption.addSerialVersionUID);
/* First load defaults */
bcelWorld.getLint().loadDefaultProperties();
/* Second overlay LTW defaults */
bcelWorld.getLint().adviceDidNotMatch.setKind(null);
/* Third load user file using -Xlintfile so that -Xlint wins */
if (weaverOption.lintFile != null) {
InputStream resource = null;
try {
resource = loader.getResourceAsStream(weaverOption.lintFile);
Exception failure = null;
if (resource != null) {
try {
Properties properties = new Properties();
properties.load(resource);
world.getLint().setFromProperties(properties);
} catch (IOException e) {
failure = e;
}
}
if (failure != null || resource == null) {
warn("Cannot access resource for -Xlintfile:"+weaverOption.lintFile,failure);
// world.getMessageHandler().handleMessage(new Message(
// "Cannot access resource for -Xlintfile:"+weaverOption.lintFile,
// IMessage.WARNING,
// failure,
// null));
}
} finally {
try { resource.close(); } catch (Throwable t) {;}
}
}
/* Fourth override with -Xlint */
if (weaverOption.lint != null) {
if (weaverOption.lint.equals("default")) {//FIXME should be AjBuildConfig.AJLINT_DEFAULT but yetanother deps..
bcelWorld.getLint().loadDefaultProperties();
} else {
bcelWorld.getLint().setAll(weaverOption.lint);
}
}
//TODO proceedOnError option
}
private void registerAspectExclude(final BcelWeaver weaver, final ClassLoader loader, final List definitions) {
String fastMatchInfo = null;
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
for (Iterator iterator1 = definition.getAspectExcludePatterns().iterator(); iterator1.hasNext();) {
String exclude = (String) iterator1.next();
TypePattern excludePattern = new PatternParser(exclude).parseTypePattern();
m_aspectExcludeTypePattern.add(excludePattern);
fastMatchInfo = looksLikeStartsWith(exclude);
if (fastMatchInfo != null) {
m_aspectExcludeStartsWith.add(fastMatchInfo);
}
}
}
}
private void registerAspectInclude(final BcelWeaver weaver, final ClassLoader loader, final List definitions) {
String fastMatchInfo = null;
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
for (Iterator iterator1 = definition.getAspectIncludePatterns().iterator(); iterator1.hasNext();) {
String include = (String) iterator1.next();
TypePattern includePattern = new PatternParser(include).parseTypePattern();
m_aspectIncludeTypePattern.add(includePattern);
fastMatchInfo = looksLikeStartsWith(include);
if (fastMatchInfo != null) {
m_aspectIncludeStartsWith.add(fastMatchInfo);
}
}
}
}
protected void lint (String name, String[] infos) {
Lint lint = bcelWorld.getLint();
Kind kind = lint.getLintKind(name);
kind.signal(infos,null,null);
}
public String getContextId () {
return weavingContext.getId();
}
/**
* Register the aspect, following include / exclude rules
*
* @param weaver
* @param loader
* @param definitions
*/
private void registerAspects(final BcelWeaver weaver, final ClassLoader loader, final List definitions) {
if (trace.isTraceEnabled()) trace.enter("registerAspects",this, new Object[] { weaver, loader, definitions} );
//TODO: the exclude aspect allow to exclude aspect defined upper in the CL hierarchy - is it what we want ??
// if not, review the getResource so that we track which resource is defined by which CL
//iterate aspectClassNames
//exclude if in any of the exclude list
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
for (Iterator aspects = definition.getAspectClassNames().iterator(); aspects.hasNext();) {
String aspectClassName = (String) aspects.next();
if (acceptAspect(aspectClassName)) {
info("register aspect " + aspectClassName);
// System.err.println("? ClassLoaderWeavingAdaptor.registerAspects() aspectName=" + aspectClassName + ", loader=" + loader + ", bundle=" + weavingContext.getClassLoaderName());
/*ResolvedType aspect = */weaver.addLibraryAspect(aspectClassName);
//generate key for SC
if(namespace==null){
namespace=new StringBuffer(aspectClassName);
}else{
namespace = namespace.append(";"+aspectClassName);
}
}
else {
// warn("aspect excluded: " + aspectClassName);
lint("aspectExcludedByConfiguration", new String[] { aspectClassName, getClassLoaderName(loader) });
}
}
}
//iterate concreteAspects
//exclude if in any of the exclude list - note that the user defined name matters for that to happen
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
for (Iterator aspects = definition.getConcreteAspects().iterator(); aspects.hasNext();) {
Definition.ConcreteAspect concreteAspect = (Definition.ConcreteAspect) aspects.next();
if (acceptAspect(concreteAspect.name)) {
ConcreteAspectCodeGen gen = new ConcreteAspectCodeGen(concreteAspect, weaver.getWorld());
if (!gen.validate()) {
error("Concrete-aspect '"+concreteAspect.name+"' could not be registered");
break;
}
this.generatedClassHandler.acceptClass(
concreteAspect.name,
gen.getBytes()
);
/*ResolvedType aspect = */weaver.addLibraryAspect(concreteAspect.name);
//generate key for SC
if(namespace==null){
namespace=new StringBuffer(concreteAspect.name);
}else{
namespace = namespace.append(";"+concreteAspect.name);
}
}
}
}
// System.out.println("ClassLoaderWeavingAdaptor.registerAspects() classloader=" + weavingContext.getClassLoaderName() + ", namespace=" + namespace);
/* We didn't register any aspects so disable the adaptor */
if (namespace == null) {
disable();
info("no aspects registered. Disabling weaver for class loader " + getClassLoaderName(loader));
}
if (trace.isTraceEnabled()) trace.exit("registerAspects",isEnabled());
}
/**
* Register the include / exclude filters
* We duplicate simple patterns in startWith filters that will allow faster matching without ResolvedType
*
* @param weaver
* @param loader
* @param definitions
*/
private void registerIncludeExclude(final BcelWeaver weaver, final ClassLoader loader, final List definitions) {
String fastMatchInfo = null;
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
for (Iterator iterator1 = definition.getIncludePatterns().iterator(); iterator1.hasNext();) {
String include = (String) iterator1.next();
TypePattern includePattern = new PatternParser(include).parseTypePattern();
m_includeTypePattern.add(includePattern);
fastMatchInfo = looksLikeStartsWith(include);
if (fastMatchInfo != null) {
m_includeStartsWith.add(fastMatchInfo);
}
}
for (Iterator iterator1 = definition.getExcludePatterns().iterator(); iterator1.hasNext();) {
String exclude = (String) iterator1.next();
TypePattern excludePattern = new PatternParser(exclude).parseTypePattern();
m_excludeTypePattern.add(excludePattern);
fastMatchInfo = looksLikeStartsWith(exclude);
if (fastMatchInfo != null) {
m_excludeStartsWith.add(fastMatchInfo);
}
}
}
}
/**
* Checks if the type pattern can be handled as a startswith check
*
* TODO AV - enhance to support "char.sss" ie FQN direclty (match iff equals)
* we could also add support for "*..*charss" endsWith style?
*
* @param typePattern
* @return null if not possible, or the startWith sequence to test against
*/
private String looksLikeStartsWith(String typePattern) {
if (typePattern.indexOf('@') >= 0
|| typePattern.indexOf('+') >= 0
|| typePattern.indexOf(' ') >= 0
|| typePattern.charAt(typePattern.length()-1) != '*') {
return null;
}
// now must looks like with "charsss..*" or "cha.rss..*" etc
// note that "*" and "*..*" won't be fast matched
// and that "charsss.*" will not neither
int length = typePattern.length();
if (typePattern.endsWith("..*") && length > 3) {
if (typePattern.indexOf("..") == length-3 // no ".." before last sequence
&& typePattern.indexOf('*') == length-1) { // no "*" before last sequence
return typePattern.substring(0, length-2).replace('$', '.');
// ie "charsss." or "char.rss." etc
}
}
return null;
}
/**
* Register the dump filter
*
* @param weaver
* @param loader
* @param definitions
*/
private void registerDump(final BcelWeaver weaver, final ClassLoader loader, final List definitions) {
for (Iterator iterator = definitions.iterator(); iterator.hasNext();) {
Definition definition = (Definition) iterator.next();
for (Iterator iterator1 = definition.getDumpPatterns().iterator(); iterator1.hasNext();) {
String dump = (String) iterator1.next();
TypePattern pattern = new PatternParser(dump).parseTypePattern();
m_dumpTypePattern.add(pattern);
}
if (definition.shouldDumpBefore()) {
m_dumpBefore = true;
}
}
}
protected boolean accept(String className, byte[] bytes) {
// avoid ResolvedType if not needed
if (m_excludeTypePattern.isEmpty() && m_includeTypePattern.isEmpty()) {
return true;
}
// still try to avoid ResolvedType if we have simple patterns
String fastClassName = className.replace('/', '.').replace('$', '.');
for (int i = 0; i < m_excludeStartsWith.size(); i++) {
if (fastClassName.startsWith((String)m_excludeStartsWith.get(i))) {
return false;
}
}
/*
* Bug 120363
* If we have an exclude pattern that cannot be matched using "starts with"
* then we cannot fast accept
*/
if (m_excludeTypePattern.isEmpty()) {
boolean fastAccept = false;//defaults to false if no fast include
for (int i = 0; i < m_includeStartsWith.size(); i++) {
fastAccept = fastClassName.startsWith((String)m_includeStartsWith.get(i));
if (fastAccept) {
break;
}
}
}
// needs further analysis
// TODO AV - needs refactoring
// during LTW this calling resolve at that stage is BAD as we do have the bytecode from the classloader hook
// but still go thru resolve that will do a getResourcesAsStream on disk
// this is also problematic for jit stub which are not on disk - as often underlying infra
// does returns null or some other info for getResourceAsStream (f.e. WLS 9 CR248491)
// Instead I parse the given bytecode. But this also means it will be parsed again in
// new WeavingClassFileProvider() from WeavingAdaptor.getWovenBytes()...
ensureDelegateInitialized(className,bytes);
ResolvedType classInfo = delegateForCurrentClass.getResolvedTypeX();//BAD: weaver.getWorld().resolve(UnresolvedType.forName(className), true);
//exclude are "AND"ed
for (Iterator iterator = m_excludeTypePattern.iterator(); iterator.hasNext();) {
TypePattern typePattern = (TypePattern) iterator.next();
if (typePattern.matchesStatically(classInfo)) {
// exclude match - skip
return false;
}
}
//include are "OR"ed
boolean accept = true;//defaults to true if no include
for (Iterator iterator = m_includeTypePattern.iterator(); iterator.hasNext();) {
TypePattern typePattern = (TypePattern) iterator.next();
accept = typePattern.matchesStatically(classInfo);
if (accept) {
break;
}
// goes on if this include did not match ("OR"ed)
}
return accept;
}
//FIXME we don't use include/exclude of others aop.xml
//this can be nice but very dangerous as well to change that
private boolean acceptAspect(String aspectClassName) {
// avoid ResolvedType if not needed
if (m_aspectExcludeTypePattern.isEmpty() && m_aspectIncludeTypePattern.isEmpty()) {
return true;
}
// still try to avoid ResolvedType if we have simple patterns
// EXCLUDE: if one match then reject
String fastClassName = aspectClassName.replace('/', '.').replace('.', '$');
for (int i = 0; i < m_aspectExcludeStartsWith.size(); i++) {
if (fastClassName.startsWith((String)m_aspectExcludeStartsWith.get(i))) {
return false;
}
}
//INCLUDE: if one match then accept
for (int i = 0; i < m_aspectIncludeStartsWith.size(); i++) {
if (fastClassName.startsWith((String)m_aspectIncludeStartsWith.get(i))) {
return true;
}
}
// needs further analysis
ResolvedType classInfo = weaver.getWorld().resolve(UnresolvedType.forName(aspectClassName), true);
//exclude are "AND"ed
for (Iterator iterator = m_aspectExcludeTypePattern.iterator(); iterator.hasNext();) {
TypePattern typePattern = (TypePattern) iterator.next();
if (typePattern.matchesStatically(classInfo)) {
// exclude match - skip
return false;
}
}
//include are "OR"ed
boolean accept = true;//defaults to true if no include
for (Iterator iterator = m_aspectIncludeTypePattern.iterator(); iterator.hasNext();) {
TypePattern typePattern = (TypePattern) iterator.next();
accept = typePattern.matchesStatically(classInfo);
if (accept) {
break;
}
// goes on if this include did not match ("OR"ed)
}
return accept;
}
protected boolean shouldDump(String className, boolean before) {
// Don't dump before weaving unless asked to
if (before && !m_dumpBefore) {
return false;
}
// avoid ResolvedType if not needed
if (m_dumpTypePattern.isEmpty()) {
return false;
}
//TODO AV - optimize for className.startWith only
ResolvedType classInfo = weaver.getWorld().resolve(UnresolvedType.forName(className), true);
//dump
for (Iterator iterator = m_dumpTypePattern.iterator(); iterator.hasNext();) {
TypePattern typePattern = (TypePattern) iterator.next();
if (typePattern.matchesStatically(classInfo)) {
// dump match
return true;
}
}
return false;
}
/*
* shared classes methods
*/
/**
* @return Returns the key.
*/
public String getNamespace() {
// System.out.println("ClassLoaderWeavingAdaptor.getNamespace() classloader=" + weavingContext.getClassLoaderName() + ", namespace=" + namespace);
if(namespace==null) return "";
else return new String(namespace);
}
/**
* Check to see if any classes are stored in the generated classes cache.
* Then flush the cache if it is not empty
* @param className TODO
* @return true if a class has been generated and is stored in the cache
*/
public boolean generatedClassesExistFor (String className) {
// System.err.println("? ClassLoaderWeavingAdaptor.generatedClassesExist() classname=" + className + ", size=" + generatedClasses);
if (className == null) return !generatedClasses.isEmpty();
else return generatedClasses.containsKey(className);
}
/**
* Flush the generated classes cache
*/
public void flushGeneratedClasses() {
// System.err.println("? ClassLoaderWeavingAdaptor.flushGeneratedClasses() generatedClasses=" + generatedClasses);
generatedClasses = new HashMap();
}
private void defineClass(ClassLoader loader, String name, byte[] bytes) {
if (trace.isTraceEnabled()) trace.enter("defineClass",this,new Object[] {loader,name,bytes});
Object clazz = null;
info("generating class '" + name + "'");
try {
//TODO av protection domain, and optimize
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", new Class[] { String.class,
bytes.getClass(), int.class, int.class });
defineClass.setAccessible(true);
clazz = defineClass.invoke(loader, new Object[] { name, bytes,
new Integer(0), new Integer(bytes.length) });
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof LinkageError) {
warn("define generated class failed",e.getTargetException());
//is already defined (happens for X$ajcMightHaveAspect interfaces since aspects are reweaved)
// TODO maw I don't think this is OK and
} else {
warn("define generated class failed",e.getTargetException());
}
} catch (Exception e) {
warn("define generated class failed",e);
}
if (trace.isTraceEnabled()) trace.exit("defineClass",clazz);
}
}