JUNEAU-169 Dynamic annotations.
diff --git a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java
index 9ba7428..1cb14a4 100644
--- a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfBeanPropertyMeta.java
@@ -43,8 +43,8 @@
 	public RdfBeanPropertyMeta(BeanPropertyMeta bpm, RdfMetaProvider mp) {

 		super(bpm);

 

-		List<Rdf> rdfs = bpm.getAllAnnotations(Rdf.class, mp);

-		List<RdfSchema> schemas = bpm.getAllAnnotations(RdfSchema.class, mp);

+		List<Rdf> rdfs = bpm.getAllAnnotations(Rdf.class);

+		List<RdfSchema> schemas = bpm.getAllAnnotations(RdfSchema.class);

 

 		for (Rdf rdf : rdfs) {

 			if (collectionFormat == RdfCollectionFormat.DEFAULT)

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
index c61fc58..6aff3ce 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
@@ -2230,7 +2230,7 @@
 				if (ci.isChildOf(PojoSwap.class))
 					lpf.add(castOrCreate(PojoSwap.class, ci.inner()));
 				else if (ci.isChildOf(Surrogate.class))
-					lpf.addAll(SurrogateSwap.findPojoSwaps(ci.inner()));
+					lpf.addAll(SurrogateSwap.findPojoSwaps(ci.inner(), this));
 				else
 					throw new FormattedRuntimeException("Invalid class {0} specified in BeanContext.pojoSwaps property.  Must be a subclass of PojoSwap or Surrogate.", ci.inner());
 			} else if (o instanceof PojoSwap) {
@@ -3011,29 +3011,252 @@
 	// MetaProvider methods
 	//-----------------------------------------------------------------------------------------------------------------
 
+	private static final boolean DISABLE_ANNOTATION_CACHING = ! Boolean.getBoolean("juneau.disableAnnotationCaching");
+
+	private TwoKeyConcurrentHashMap<Class<?>,Class<? extends Annotation>,Optional<Annotation>> classAnnotationCache = new TwoKeyConcurrentHashMap<>();
+	private TwoKeyConcurrentHashMap<Class<?>,Class<? extends Annotation>,Optional<Annotation>> declaredClassAnnotationCache = new TwoKeyConcurrentHashMap<>();
+	private TwoKeyConcurrentHashMap<Method,Class<? extends Annotation>,Optional<Annotation>> methodAnnotationCache = new TwoKeyConcurrentHashMap<>();
+	private TwoKeyConcurrentHashMap<Field,Class<? extends Annotation>,Optional<Annotation>> fieldAnnotationCache = new TwoKeyConcurrentHashMap<>();
+	private TwoKeyConcurrentHashMap<Constructor<?>,Class<? extends Annotation>,Optional<Annotation>> constructorAnnotationCache = new TwoKeyConcurrentHashMap<>();
+
 	@Override /* MetaProvider */
 	public <A extends Annotation> A getAnnotation(Class<A> a, Class<?> c) {
-		return a == null || c == null ? null : (A)annotations.findFirst(c, a).orElse(c.getAnnotation(a));
+		if (a == null || c == null)
+			return null;
+		if (DISABLE_ANNOTATION_CACHING)
+			return (A)annotations.findFirst(c, a).orElse(c.getAnnotation(a));
+		Optional<Annotation> aa = classAnnotationCache.get(c, a);
+		if (aa == null) {
+			aa = Optional.ofNullable((A)annotations.findFirst(c, a).orElse(c.getAnnotation(a)));
+			classAnnotationCache.put(c, a, aa);
+		}
+		return (A)aa.orElse(null);
+	}
+
+	/**
+	 * Finds the specified annotation on the specified class.
+	 *
+	 * @param <A> The annotation type to find.
+	 * @param a The annotation type to find.
+	 * @param c The class to search on.
+	 * @return The annotation, or <jk>null</jk> if not found.
+	 */
+	public <A extends Annotation> A getAnnotation(Class<A> a, ClassInfo c) {
+		return getAnnotation(a, c == null ? null : c.inner());
 	}
 
 	@Override /* MetaProvider */
 	public <A extends Annotation> A getDeclaredAnnotation(Class<A> a, Class<?> c) {
-		return a == null || c == null ? null : (A)annotations.findFirst(c, a).orElse(c.getAnnotation(a));
+		if (a == null || c == null)
+			return null;
+		if (DISABLE_ANNOTATION_CACHING)
+			return (A)annotations.findFirst(c, a).orElse(c.getDeclaredAnnotation(a));
+		Optional<Annotation> aa = declaredClassAnnotationCache.get(c, a);
+		if (aa == null) {
+			aa =  Optional.ofNullable((A)annotations.findFirst(c, a).orElse(c.getDeclaredAnnotation(a)));
+			declaredClassAnnotationCache.put(c, a, aa);
+		}
+		return (A)aa.orElse(null);
+	}
+
+	/**
+	 * Finds the specified declared annotation on the specified class.
+	 *
+	 * @param <A> The annotation type to find.
+	 * @param a The annotation type to find.
+	 * @param c The class to search on.
+	 * @return The annotation, or <jk>null</jk> if not found.
+	 */
+	public <A extends Annotation> A getDeclaredAnnotation(Class<A> a, ClassInfo c) {
+		return getDeclaredAnnotation(a, c == null ? null : c.inner());
 	}
 
 	@Override /* MetaProvider */
 	public <A extends Annotation> A getAnnotation(Class<A> a, Method m) {
-		return a == null || m == null ? null : (A)annotations.findFirst(m, a).orElse(m.getAnnotation(a));
+		if (a == null || m == null)
+			return null;
+		if (DISABLE_ANNOTATION_CACHING)
+			return (A)annotations.findFirst(m, a).orElse(m.getAnnotation(a));
+		Optional<Annotation> aa = methodAnnotationCache.get(m, a);
+		if (aa == null) {
+			aa =  Optional.ofNullable((A)annotations.findFirst(m, a).orElse(m.getAnnotation(a)));
+			methodAnnotationCache.put(m, a, aa);
+		}
+		return (A)aa.orElse(null);
+	}
+
+	/**
+	 * Finds the specified annotation on the specified method.
+	 *
+	 * @param <A> The annotation type to find.
+	 * @param a The annotation type to find.
+	 * @param m The method to search on.
+	 * @return The annotation, or <jk>null</jk> if not found.
+	 */
+	public <A extends Annotation> A getAnnotation(Class<A> a, MethodInfo m) {
+		return getAnnotation(a, m == null ? null : m.inner());
 	}
 
 	@Override /* MetaProvider */
 	public <A extends Annotation> A getAnnotation(Class<A> a, Field f) {
-		return a == null || f == null ? null : (A)annotations.findFirst(f, a).orElse(f.getAnnotation(a));
+		if (a == null || f == null)
+			return null;
+		if (DISABLE_ANNOTATION_CACHING)
+			return (A)annotations.findFirst(f, a).orElse(f.getAnnotation(a));
+		Optional<Annotation> aa = fieldAnnotationCache.get(f, a);
+		if (aa == null) {
+			aa =  Optional.ofNullable((A)annotations.findFirst(f, a).orElse(f.getAnnotation(a)));
+			fieldAnnotationCache.put(f, a, aa);
+		}
+		return (A)aa.orElse(null);
+	}
+
+	/**
+	 * Finds the specified annotation on the specified field.
+	 *
+	 * @param <A> The annotation type to find.
+	 * @param a The annotation type to find.
+	 * @param f The field to search on.
+	 * @return The annotation, or <jk>null</jk> if not found.
+	 */
+	public <A extends Annotation> A getAnnotation(Class<A> a, FieldInfo f) {
+		return getAnnotation(a, f == null ? null: f.inner());
 	}
 
 	@Override /* MetaProvider */
 	public <A extends Annotation> A getAnnotation(Class<A> a, Constructor<?> c) {
-		return a == null || c == null ? null : (A)annotations.findFirst(c, a).orElse(c.getAnnotation(a));
+		if (a == null || c == null)
+			return null;
+		if (DISABLE_ANNOTATION_CACHING)
+			return (A)annotations.findFirst(c, a).orElse(c.getAnnotation(a));
+		Optional<Annotation> aa = constructorAnnotationCache.get(c, a);
+		if (aa == null) {
+			aa =  Optional.ofNullable((A)annotations.findFirst(c, a).orElse(c.getAnnotation(a)));
+			constructorAnnotationCache.put(c, a, aa);
+		}
+		return (A)aa.orElse(null);
+	}
+
+	/**
+	 * Finds the specified annotation on the specified constructor.
+	 *
+	 * @param <A> The annotation type to find.
+	 * @param a The annotation type to find.
+	 * @param c The constructor to search on.
+	 * @return The annotation, or <jk>null</jk> if not found.
+	 */
+	public <A extends Annotation> A getAnnotation(Class<A> a, ConstructorInfo c) {
+		return getAnnotation(a, c == null ? null : c.inner());
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,c)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param c The class being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified class.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, Class<?> c) {
+		return getAnnotation(a, c) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,c)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param c The class being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified class.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, ClassInfo c) {
+		return getAnnotation(a, c == null ? null : c.inner()) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,c)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param c The class being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified class.
+	 */
+	public <A extends Annotation> boolean hasDeclaredAnnotation(Class<A> a, Class<?> c) {
+		return getDeclaredAnnotation(a, c) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getDeclaredAnnotation(a,c)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param c The class being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified class.
+	 */
+	public <A extends Annotation> boolean hasDeclaredAnnotation(Class<A> a, ClassInfo c) {
+		return getDeclaredAnnotation(a, c == null ? null : c.inner()) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,m)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param m The method being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified method.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, Method m) {
+		return getAnnotation(a, m) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,m)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param m The method being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified method.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, MethodInfo m) {
+		return getAnnotation(a, m == null ? null : m.inner()) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,f)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param f The field being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified field.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, Field f) {
+		return getAnnotation(a, f) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,f)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param f The field being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified field.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, FieldInfo f) {
+		return getAnnotation(a, f == null ? null : f.inner()) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,c)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param c The constructor being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified constructor.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, Constructor<?> c) {
+		return getAnnotation(a, c) != null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <c>getAnnotation(a,c)</c> returns a non-null value.
+	 *
+	 * @param a The annotation being checked for.
+	 * @param c The constructor being checked on.
+	 * @return <jk>true</jk> if the annotation exists on the specified constructor.
+	 */
+	public <A extends Annotation> boolean hasAnnotation(Class<A> a, ConstructorInfo c) {
+		return getAnnotation(a, c == null ? null : c.inner()) != null;
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 0f01aab..5945ada 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -233,11 +233,11 @@
 						}

 						constructor.setAccessible();

 					}

-					if (x.hasAnnotation(Beanc.class)) {

+					if (ctx.hasAnnotation(Beanc.class, x)) {

 						if (constructor != null)

 							throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found.");

 						constructor = x;

-						constructorArgs = split(x.getAnnotation(Beanc.class).properties());

+						constructorArgs = split(ctx.getAnnotation(Beanc.class, x).properties());

 						if (constructorArgs.length != x.getParamCount()) {

 							if (constructorArgs.length != 0)

 								throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor.");

@@ -320,7 +320,7 @@
 

 				} else /* Use 'better' introspection */ {

 

-					for (Field f : findBeanFields(c2, stopClass, fVis, filterProps)) {

+					for (Field f : findBeanFields(ctx, c2, stopClass, fVis, filterProps)) {

 						String name = findPropertyName(f, fixedBeanProps);

 						if (name != null) {

 							if (! normalProps.containsKey(name))

@@ -329,7 +329,7 @@
 						}

 					}

 

-					List<BeanMethod> bms = findBeanMethods(c2, stopClass, mVis, fixedBeanProps, filterProps, propertyNamer, fluentSetters);

+					List<BeanMethod> bms = findBeanMethods(ctx, c2, stopClass, mVis, fixedBeanProps, filterProps, propertyNamer, fluentSetters);

 

 					// Iterate through all the getters.

 					for (BeanMethod bm : bms) {

@@ -345,7 +345,7 @@
 								if (m.getAnnotation(BeanProperty.class) == null && bpm.getter.getAnnotation(BeanProperty.class) != null)

 									m = bpm.getter;  // @BeanProperty annotated method takes precedence.

 

-								else if (m.getAnnotation(Beanp.class) == null && bpm.getter.getAnnotation(Beanp.class) != null)

+								else if (! ctx.hasAnnotation(Beanp.class, m) && ctx.hasAnnotation(Beanp.class, bpm.getter))

 									m = bpm.getter;  // @Beanp annotated method takes precedence.

 

 								else if (m.getName().startsWith("is") && bpm.getter.getName().startsWith("get"))

@@ -510,8 +510,8 @@
 		private String findPropertyName(Field f, Set<String> fixedBeanProps) {

 			@SuppressWarnings("deprecation")

 			BeanProperty px = f.getAnnotation(BeanProperty.class);

-			Beanp p = f.getAnnotation(Beanp.class);

-			Name n = f.getAnnotation(Name.class);

+			Beanp p = ctx.getAnnotation(Beanp.class, f);

+			Name n = ctx.getAnnotation(Name.class, f);

 			String name = bpName(px, p, n);

 			if (isNotEmpty(name)) {

 				if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))

@@ -633,7 +633,7 @@
 	 * @param fixedBeanProps Only include methods whose properties are in this list.

 	 * @param pn Use this property namer to determine property names from the method names.

 	 */

-	static final List<BeanMethod> findBeanMethods(Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, Set<String> filterProps, PropertyNamer pn, boolean fluentSetters) {

+	static final List<BeanMethod> findBeanMethods(BeanContext ctx, Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, Set<String> filterProps, PropertyNamer pn, boolean fluentSetters) {

 		List<BeanMethod> l = new LinkedList<>();

 

 		for (ClassInfo c2 : findClasses(c, stopClass)) {

@@ -645,14 +645,14 @@
 				if (m.getParamCount() > 2)

 					continue;

 

-				BeanIgnore bi = m.getAnnotation(BeanIgnore.class);

+				BeanIgnore bi = ctx.getAnnotation(BeanIgnore.class, m);

 				if (bi != null)

 					continue;

 

 				@SuppressWarnings("deprecation")

 				BeanProperty px = m.getAnnotation(BeanProperty.class);

-				Beanp p = m.getAnnotation(Beanp.class);

-				Name n2 = m.getAnnotation(Name.class);

+				Beanp p = ctx.getAnnotation(Beanp.class, m);

+				Name n2 = ctx.getAnnotation(Name.class, m);

 				if (! (m.isVisible(v) || px != null || p != null || n2 != null))

 					continue;

 

@@ -746,7 +746,7 @@
 		return l;

 	}

 

-	static final Collection<Field> findBeanFields(Class<?> c, Class<?> stopClass, Visibility v, Set<String> filterProps) {

+	static final Collection<Field> findBeanFields(BeanContext ctx, Class<?> c, Class<?> stopClass, Visibility v, Set<String> filterProps) {

 		List<Field> l = new LinkedList<>();

 		for (ClassInfo c2 : findClasses(c, stopClass)) {

 			for (FieldInfo f : c2.getDeclaredFields()) {

@@ -757,8 +757,8 @@
 

 				@SuppressWarnings("deprecation")

 				BeanProperty px = f.getAnnotation(BeanProperty.class);

-				Beanp p = f.getAnnotation(Beanp.class);

-				Name n = f.getAnnotation(Name.class);

+				Beanp p = ctx.getAnnotation(Beanp.class, f);

+				Name n = ctx.getAnnotation(Name.class, f);

 				String bpName = bpName(px, p, n);

 

 				if (! (v.isVisible(f.inner()) || px != null || p != null))

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index 50c8fe0..56fb06c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -160,14 +160,14 @@
 		}

 

 		@SuppressWarnings("deprecation")

-		boolean validate(BeanContext f, BeanRegistry parentBeanRegistry, Map<Class<?>,Class<?>[]> typeVarImpls, Set<String> bpro, Set<String> bpwo) throws Exception {

+		boolean validate(BeanContext bc, BeanRegistry parentBeanRegistry, Map<Class<?>,Class<?>[]> typeVarImpls, Set<String> bpro, Set<String> bpwo) throws Exception {

 

 			List<Class<?>> bdClasses = new ArrayList<>();

 

 			if (field == null && getter == null && setter == null)

 				return false;

 

-			if (field == null && setter == null && f.isBeansRequireSettersForGetters() && ! isConstructorArg)

+			if (field == null && setter == null && bc.isBeansRequireSettersForGetters() && ! isConstructorArg)

 				return false;

 

 			canRead |= (field != null || getter != null);

@@ -175,11 +175,11 @@
 

 			if (innerField != null) {

 				BeanProperty px = innerField.getAnnotation(BeanProperty.class);

-				Beanp p = innerField.getAnnotation(Beanp.class);

+				Beanp p = bc.getAnnotation(Beanp.class, innerField);

 				if (field != null || px != null || p != null) {

 					// Only use field type if it's a bean property or has @Beanp annotation.

 					// Otherwise, we want to infer the type from the getter or setter.

-					rawTypeMeta = f.resolveClassMeta(px, p, innerField.getGenericType(), typeVarImpls);

+					rawTypeMeta = bc.resolveClassMeta(px, p, innerField.getGenericType(), typeVarImpls);

 					isUri |= (rawTypeMeta.isUri());

 				}

 				if (px != null) {

@@ -196,19 +196,19 @@
 					if (! p.wo().isEmpty())

 						writeOnly = Boolean.valueOf(p.wo());

 				}

-				Swap s = innerField.getAnnotation(Swap.class);

+				Swap s = bc.getAnnotation(Swap.class, innerField);

 				if (s != null) {

 					swap = getPropertyPojoSwap(s);

 				}

-				isUri |= innerField.isAnnotationPresent(org.apache.juneau.annotation.URI.class);

+				isUri |= bc.getAnnotation(org.apache.juneau.annotation.URI.class, innerField) != null;

 			}

 

 			if (getter != null) {

 				BeanProperty px = MethodInfo.of(getter).getAnnotation(BeanProperty.class);

-				Beanp p = MethodInfo.of(getter).getAnnotation(Beanp.class);

+				Beanp p = bc.getAnnotation(Beanp.class, getter);

 				if (rawTypeMeta == null)

-					rawTypeMeta = f.resolveClassMeta(px, p, getter.getGenericReturnType(), typeVarImpls);

-				isUri |= (rawTypeMeta.isUri() || getter.isAnnotationPresent(org.apache.juneau.annotation.URI.class));

+					rawTypeMeta = bc.resolveClassMeta(px, p, getter.getGenericReturnType(), typeVarImpls);

+				isUri |= (rawTypeMeta.isUri() || bc.hasAnnotation(org.apache.juneau.annotation.URI.class, getter));

 				if (px != null) {

 					if (properties != null && ! px.properties().isEmpty())

 						properties = split(px.properties());

@@ -223,7 +223,7 @@
 					if (! p.wo().isEmpty())

 						writeOnly = Boolean.valueOf(p.wo());

 				}

-				Swap s = getter.getAnnotation(Swap.class);

+				Swap s = bc.getAnnotation(Swap.class, getter);

 				if (s != null && swap == null) {

 					swap = getPropertyPojoSwap(s);

 				}

@@ -231,9 +231,9 @@
 

 			if (setter != null) {

 				BeanProperty px = MethodInfo.of(setter).getAnnotation(BeanProperty.class);

-				Beanp p = MethodInfo.of(setter).getAnnotation(Beanp.class);

+				Beanp p = bc.getAnnotation(Beanp.class, setter);

 				if (rawTypeMeta == null)

-					rawTypeMeta = f.resolveClassMeta(px, p, setter.getGenericParameterTypes()[0], typeVarImpls);

+					rawTypeMeta = bc.resolveClassMeta(px, p, setter.getGenericParameterTypes()[0], typeVarImpls);

 				isUri |= (rawTypeMeta.isUri() || setter.isAnnotationPresent(org.apache.juneau.annotation.URI.class));

 				if (px != null) {

 					if (swap == null)

@@ -253,7 +253,7 @@
 					if (! p.wo().isEmpty())

 						writeOnly = Boolean.valueOf(p.wo());

 				}

-				Swap s = setter.getAnnotation(Swap.class);

+				Swap s = bc.getAnnotation(Swap.class, setter);

 				if (s != null && swap == null) {

 					swap = getPropertyPojoSwap(s);

 				}

@@ -1125,31 +1125,31 @@
 	 *

 	 * @param <A> The class to find annotations for.

 	 * @param a The class to find annotations for.

-	 * @param mp The metadata provider for finding annotations.

 	 * @return A list of annotations ordered in child-to-parent order.  Never <jk>null</jk>.

 	 */

-	public <A extends Annotation> List<A> getAllAnnotations(Class<A> a, MetaProvider mp) {

+	public <A extends Annotation> List<A> getAllAnnotations(Class<A> a) {

 		List<A> l = new LinkedList<>();

+		BeanContext bc = beanContext;

 		if (a == null)

 			return l;

 		if (field != null) {

-			addIfNotNull(l, mp.getAnnotation(a, field));

-			ClassInfo.of(field.getType()).appendAnnotations(l, a, mp);

+			addIfNotNull(l, bc.getAnnotation(a, field));

+			ClassInfo.of(field.getType()).appendAnnotations(l, a, bc);

 		}

 		if (getter != null) {

-			addIfNotNull(l, mp.getAnnotation(a, getter));

-			ClassInfo.of(getter.getReturnType()).appendAnnotations(l, a, mp);

+			addIfNotNull(l, bc.getAnnotation(a, getter));

+			ClassInfo.of(getter.getReturnType()).appendAnnotations(l, a, bc);

 		}

 		if (setter != null) {

-			addIfNotNull(l, mp.getAnnotation(a, setter));

-			ClassInfo.of(setter.getReturnType()).appendAnnotations(l, a, mp);

+			addIfNotNull(l, bc.getAnnotation(a, setter));

+			ClassInfo.of(setter.getReturnType()).appendAnnotations(l, a, bc);

 		}

 		if (extraKeys != null) {

-			addIfNotNull(l, mp.getAnnotation(a, extraKeys));

-			ClassInfo.of(extraKeys.getReturnType()).appendAnnotations(l, a, mp);

+			addIfNotNull(l, bc.getAnnotation(a, extraKeys));

+			ClassInfo.of(extraKeys.getReturnType()).appendAnnotations(l, a, bc);

 		}

 

-		getBeanMeta().getClassMeta().getInfo().appendAnnotations(l, a, mp);

+		getBeanMeta().getClassMeta().getInfo().appendAnnotations(l, a, bc);

 		return l;

 	}

 

@@ -1158,16 +1158,16 @@
 	 *

 	 * @param <A> The class to find annotations for.

 	 * @param a The class to find annotations for.

-	 * @param mp The metadata provider for finding annotations.

 	 * @return A list of annotations ordered in child-to-parent order.  Never <jk>null</jk>.

 	 */

-	public <A extends Annotation> List<A> getAnnotations(Class<A> a, MetaProvider mp) {

+	public <A extends Annotation> List<A> getAnnotations(Class<A> a) {

 		List<A> l = new LinkedList<>();

+		BeanContext bc = beanContext;

 		if (a == null)

 			return l;

-		addIfNotNull(l, mp.getAnnotation(a, field));

-		addIfNotNull(l, mp.getAnnotation(a, getter));

-		addIfNotNull(l, mp.getAnnotation(a, setter));

+		addIfNotNull(l, bc.getAnnotation(a, field));

+		addIfNotNull(l, bc.getAnnotation(a, getter));

+		addIfNotNull(l, bc.getAnnotation(a, setter));

 		return l;

 	}

 

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java
index b643d3a..f39ba23 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java
@@ -89,7 +89,7 @@
 						addToMap(typeName, val);
 					}
 				} else {
-					Bean b = ci.getAnnotation(Bean.class);
+					Bean b = ci.getAnnotation(Bean.class, beanContext);
 					if (b == null || b.typeName().isEmpty())
 						throw new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", c.getName());
 					addToMap(b.typeName(), beanContext.getClassMeta(c));
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
index 8301fb4..4cfd4b0 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
@@ -120,8 +120,6 @@
 				return null;
 			isBottom = false;
 			stack.add(new StackElement(stack.size(), attrName, o, cm));
-			if (isDebug())
-				getLogger().info(getStack(false));
 			set.put(o, o);
 		}
 		return cm;
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index 550e82b..c0cd6f4 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -351,6 +351,7 @@
 		ClassMetaBuilder(Class<T> innerClass, BeanContext beanContext, Class<? extends T> implClass, BeanFilter beanFilter, PojoSwap<T,?>[] pojoSwaps, PojoSwap<?,?>[] childPojoSwaps, Object example) {

 			this.innerClass = innerClass;

 			this.beanContext = beanContext;

+			BeanContext bc = beanContext;

 

 			this.implClass = implClass;

 			ClassInfo ici = ClassInfo.of(implClass);

@@ -534,15 +535,15 @@
 			}

 

 			if (beanFilter == null)

-				beanFilter = findBeanFilter();

+				beanFilter = findBeanFilter(bc);

 

 			if (pojoSwaps != null)

 				this.pojoSwaps.addAll(Arrays.asList(pojoSwaps));

 

-			if (beanContext != null)

-				this.builderSwap = BuilderSwap.findSwapFromPojoClass(c, beanContext.getBeanConstructorVisibility(), beanContext.getBeanMethodVisibility());

+			if (bc != null)

+				this.builderSwap = BuilderSwap.findSwapFromPojoClass(c, bc.getBeanConstructorVisibility(), bc.getBeanMethodVisibility());

 

-			findPojoSwaps(this.pojoSwaps);

+			findPojoSwaps(this.pojoSwaps, bc);

 

 			try {

 

@@ -578,7 +579,7 @@
 

 					BeanMeta newMeta = null;

 					try {

-						newMeta = new BeanMeta(ClassMeta.this, beanContext, beanFilter, null);

+						newMeta = new BeanMeta(ClassMeta.this, bc, beanFilter, null);

 						notABeanReason = newMeta.notABeanReason;

 

 						// Always get these even if it's not a bean:

@@ -603,22 +604,22 @@
 			if (beanMeta != null)

 				dictionaryName = beanMeta.getDictionaryName();

 

-			if (beanMeta != null && beanContext != null && beanContext.isUseInterfaceProxies() && innerClass.isInterface())

+			if (beanMeta != null && bc != null && bc.isUseInterfaceProxies() && innerClass.isInterface())

 				invocationHandler = new BeanProxyInvocationHandler<T>(beanMeta);

 

-			Bean b = c.getAnnotation(Bean.class);

+			Bean b = bc == null ? null : bc.getAnnotation(Bean.class, c);

 			if (b != null) {

 				if (b.beanDictionary().length != 0)

-					beanRegistry = new BeanRegistry(beanContext, null, b.beanDictionary());

+					beanRegistry = new BeanRegistry(bc, null, b.beanDictionary());

 				if (b.dictionary().length != 0)

-					beanRegistry = new BeanRegistry(beanContext, null, b.dictionary());

+					beanRegistry = new BeanRegistry(bc, null, b.dictionary());

 

 				// This could be a non-bean POJO with a type name.

 				if (dictionaryName == null && ! b.typeName().isEmpty())

 					dictionaryName = b.typeName();

 			}

 

-			Example e = c.getAnnotation(Example.class);

+			Example e = bc == null ? null : bc.getAnnotation(Example.class, c);

 

 			if (example == null && e != null && ! e.value().isEmpty())

 				example = e.value();

@@ -678,9 +679,9 @@
 			this.stringMutater = Mutaters.get(String.class, c);

 		}

 

-		private BeanFilter findBeanFilter() {

+		private BeanFilter findBeanFilter(BeanContext bc) {

 			try {

-				List<Bean> ba = info.getAnnotations(Bean.class);

+				List<Bean> ba = info.getAnnotations(Bean.class, bc);

 				if (! ba.isEmpty())

 					return new AnnotationBeanFilterBuilder(innerClass, ba).build();

 			} catch (Exception e) {

@@ -689,11 +690,11 @@
 			return null;

 		}

 

-		private void findPojoSwaps(List<PojoSwap> l) {

-			Swap swap = innerClass.getAnnotation(Swap.class);

+		private void findPojoSwaps(List<PojoSwap> l, BeanContext bc) {

+			Swap swap = bc == null ? null : bc.getAnnotation(Swap.class, innerClass);

 			if (swap != null)

 				l.add(createPojoSwap(swap));

-			Swaps swaps = innerClass.getAnnotation(Swaps.class);

+			Swaps swaps = bc == null ? null : bc.getAnnotation(Swaps.class, innerClass);

 			if (swaps != null)

 				for (Swap s : swaps.value())

 					l.add(createPojoSwap(s));

@@ -727,7 +728,7 @@
 			}

 

 			if (ci.isChildOf(Surrogate.class)) {

-				List<SurrogateSwap<?,?>> l = SurrogateSwap.findPojoSwaps(c);

+				List<SurrogateSwap<?,?>> l = SurrogateSwap.findPojoSwaps(c, beanContext);

 				if (! l.isEmpty())

 					return (PojoSwap<T,?>)l.iterator().next();

 			}

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
index 6ad4d2b..8877b6d 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
@@ -316,10 +316,9 @@
 			throws IOException, ParseException, XMLStreamException {

 		String href = r.getAttributeValue(null, "href");

 		String name = getElementText(r);

-		Class<T> beanClass = beanType.getInnerClass();

-		if (beanClass.isAnnotationPresent(HtmlLink.class)) {

-			HtmlLink h = beanClass.getAnnotation(HtmlLink.class);

-			BeanMap<T> m = newBeanMap(beanClass);

+		if (beanType.hasAnnotation(HtmlLink.class)) {

+			HtmlLink h = beanType.getAnnotation(HtmlLink.class);

+			BeanMap<T> m = newBeanMap(beanType.getInnerClass());

 			m.put(h.uriProperty(), href);

 			m.put(h.nameProperty(), name);

 			return m.getBean();

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index fff755e..9a5ac72 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -345,9 +345,8 @@
 

 			} else if (sType.isBean()) {

 				BeanMap m = toBeanMap(o);

-				Class<?> c = o.getClass();

-				if (c.isAnnotationPresent(HtmlLink.class)) {

-					HtmlLink h = o.getClass().getAnnotation(HtmlLink.class);

+				if (aType.hasAnnotation(HtmlLink.class)) {

+					HtmlLink h = aType.getAnnotation(HtmlLink.class);

 					Object urlProp = m.get(h.uriProperty());

 					Object nameProp = m.get(h.nameProperty());

 					out.oTag("a").attrUri("href", urlProp).append('>').text(nameProp).eTag("a");

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanMeta.java
index c4426ee..8e9daad 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanMeta.java
@@ -95,9 +95,8 @@
 		}
 
 		Builder apply(Class<?> c) {
-			ClassInfo ci = ClassInfo.of(c);
-			apply(ci.getAnnotation(Request.class));
 			this.cm = BeanContext.DEFAULT.getClassMeta(c);
+			apply(cm.getAnnotation(Request.class));
 			for (MethodInfo m : cm.getInfo().getAllMethods()) {
 
 				if (m.isPublic()) {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaBeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaBeanPropertyMeta.java
index f365f01..f7008a9 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaBeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaBeanPropertyMeta.java
@@ -41,7 +41,7 @@
 		this.schema = new ObjectMap();

 

 		try {

-			for (Schema s : bpm.getAnnotations(Schema.class, mp))

+			for (Schema s : bpm.getAnnotations(Schema.class))

 				schema.appendAll(SchemaUtils.asMap(s));

 		} catch (ParseException e) {

 			throw new RuntimeException(e);

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/SurrogateSwap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/SurrogateSwap.java
index 53290f6..f7a1ca1 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/SurrogateSwap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/SurrogateSwap.java
@@ -53,14 +53,15 @@
 	 * Returns an empty list if no public 1-arg constructors are found.

 	 *

 	 * @param c The surrogate class.

+	 * @param bc The bean context to use for looking up annotations.

 	 * @return The list of POJO swaps that apply to this class.

 	 */

 	@SuppressWarnings({"unchecked", "rawtypes"})

-	public static List<SurrogateSwap<?,?>> findPojoSwaps(Class<?> c) {

+	public static List<SurrogateSwap<?,?>> findPojoSwaps(Class<?> c, BeanContext bc) {

 		List<SurrogateSwap<?,?>> l = new LinkedList<>();

 		ClassInfo ci = ClassInfo.of(c);

 		for (ConstructorInfo cc : ci.getPublicConstructors()) {

-			if (cc.getAnnotation(BeanIgnore.class) == null && cc.hasNumParams(1) && cc.isPublic()) {

+			if (! bc.hasAnnotation(BeanIgnore.class, cc) && cc.hasNumParams(1) && cc.isPublic()) {

 				Class<?> pt = cc.getRawParamType(0);

 				if (! pt.equals(c.getDeclaringClass())) {

 					// Find the unswap method if there is one.

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/TwoKeyConcurrentHashMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/TwoKeyConcurrentHashMap.java
new file mode 100644
index 0000000..6a4829a
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/TwoKeyConcurrentHashMap.java
@@ -0,0 +1,75 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.utils;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * A hashmap that allows for two-part keys.
+ * @param <K1> Key part 1 type.
+ * @param <K2> Key part 2 type.
+ * @param <V> Value type.
+ */
+public class TwoKeyConcurrentHashMap<K1,K2,V> extends ConcurrentHashMap<TwoKeyConcurrentHashMap.Key<K1,K2>,V> {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Adds an entry to this map.
+	 *
+	 * @param key1 Key part 1.  Can be <jk>null</jk>.
+	 * @param key2 Key part 2.  Can be <jk>null</jk>.
+	 * @param value Value.
+	 * @return The previous value if there was one.
+	 */
+	public V put(K1 key1, K2 key2, V value) {
+		Key<K1,K2> key = new Key<>(key1, key2);
+		return super.put(key, value);
+	}
+
+	/**
+	 * Retrieves an entry from this map.
+	 *
+	 * @param key1 Key part 1.  Can be <jk>null</jk>.
+	 * @param key2 Key part 2.  Can be <jk>null</jk>.
+	 * @return The previous value if there was one.
+	 */
+	public V get(K1 key1, K2 key2) {
+		Key<K1,K2> key = new Key<>(key1, key2);
+		return super.get(key);
+	}
+
+	static class Key<K1,K2> {
+		final K1 k1;
+		final K2 k2;
+		final int hashCode;
+
+		Key(K1 k1, K2 k2) {
+			this.k1 = k1;
+			this.k2 = k2;
+			this.hashCode = 31*(k1 == null ? 0 : k1.hashCode()) + (k2 == null ? 0 : k2.hashCode());
+		}
+
+		@Override /* Object */
+		public int hashCode() {
+			return hashCode;
+		}
+
+		@Override /* Object */
+		@SuppressWarnings("unchecked")
+		public boolean equals(Object o) {
+			Key<K1,K2> ko = (Key<K1,K2>)o;
+			return Objects.equals(k1, ko.k1) && Objects.equals(k2, ko.k2);
+		}
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
index 6687e96..66a1740 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
@@ -43,7 +43,7 @@
 		super(bpm);

 		this.xmlMetaProvider = mp;

 

-		for (Xml xml : bpm.getAnnotations(Xml.class, mp))

+		for (Xml xml : bpm.getAnnotations(Xml.class))

 			findXmlInfo(xml, mp);

 

 		if (namespace == null)

@@ -104,8 +104,8 @@
 		ClassMeta<?> cmBean = bpm.getBeanMeta().getClassMeta();

 		String name = bpm.getName();

 

-		List<Xml> xmls = bpm.getAllAnnotations(Xml.class, mp);

-		List<XmlSchema> schemas = bpm.getAllAnnotations(XmlSchema.class, mp);

+		List<Xml> xmls = bpm.getAllAnnotations(Xml.class);

+		List<XmlSchema> schemas = bpm.getAllAnnotations(XmlSchema.class);

 		namespace = XmlUtils.findNamespace(xmls, schemas);

 

 		if (xmlFormat == XmlFormat.DEFAULT)