modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java | 78 +---
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java | 67 ++-
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java | 75 +++
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecoratorPicker.java | 162 --------
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java | 71 +++
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java | 46 ++
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java | 195 ++++++++++
modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java | 59 +++
modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java | 145 +++++++
modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java | 180 +++++++++
10 files changed, 861 insertions(+), 217 deletions(-)
New commits:
commit 772e9d247e524071a86d6a25b8e1615ed77e9fcf
Author: Lukas Krejci <lkrejci(a)redhat.com>
Date: Mon Jan 9 14:05:09 2012 +0100
Fixing the application of various decorators to JNDI contexts to support
contexts that implement more than 1 context interface (like LdapCtx which
implements both LdapContext and EventDirContext).
This should fix the RHQ's LDAP integration for good.
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java
index 60e9bf6..3046af7 100644
--- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java
@@ -27,7 +27,7 @@ import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
@@ -37,6 +37,10 @@ import java.util.Set;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.event.EventContext;
+import javax.naming.event.EventDirContext;
+import javax.naming.ldap.LdapContext;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;
@@ -45,16 +49,10 @@ import org.apache.commons.logging.LogFactory;
import org.jnp.interfaces.NamingContextFactory;
import org.rhq.jndi.context.AccessCheckingContextDecorator;
-import org.rhq.jndi.context.AccessCheckingDirContextDecorator;
-import org.rhq.jndi.context.AccessCheckingEventContextDecorator;
-import org.rhq.jndi.context.AccessCheckingEventDirContextDecorator;
-import org.rhq.jndi.context.AccessCheckingLdapContextDecorator;
-import org.rhq.jndi.context.ContextDecoratorPicker;
-import org.rhq.jndi.context.URLPreferringContextDecorator;
-import org.rhq.jndi.context.URLPreferringDirContextDecorator;
-import org.rhq.jndi.context.URLPreferringEventContextDecorator;
-import org.rhq.jndi.context.URLPreferringEventDirContextDecorator;
-import org.rhq.jndi.context.URLPreferringLdapContextDecorator;
+import org.rhq.jndi.context.AccessCheckingContextDecoratorSetContext;
+import org.rhq.jndi.context.ContextDecorator;
+import org.rhq.jndi.context.URLPreferringContextDecoratorSetContext;
+import org.rhq.jndi.util.DecoratorPicker;
/**
* This initial context factory builder is installed early on during the RHQ server startup
@@ -89,6 +87,17 @@ public class AccessCheckingInitialContextFactoryBuilder implements InitialContex
*/
private static final String[] CHECKED_SCHEMES = { "java" };
+ private static final Set<Class<? extends Context>> SUPPORTED_CONTEXT_INTERFACES;
+
+ static {
+ SUPPORTED_CONTEXT_INTERFACES = new HashSet<Class<? extends Context>>();
+ SUPPORTED_CONTEXT_INTERFACES.add(Context.class);
+ SUPPORTED_CONTEXT_INTERFACES.add(DirContext.class);
+ SUPPORTED_CONTEXT_INTERFACES.add(EventContext.class);
+ SUPPORTED_CONTEXT_INTERFACES.add(EventDirContext.class);
+ SUPPORTED_CONTEXT_INTERFACES.add(LdapContext.class);
+ }
+
private static final Set<InetAddress> SERVER_BIND_IPS;
static {
SERVER_BIND_IPS = new HashSet<InetAddress>();
@@ -191,42 +200,31 @@ public class AccessCheckingInitialContextFactoryBuilder implements InitialContex
}
private static InitialContextFactory getAccessCheckingFactory(InitialContextFactory original) {
- return new DecoratingInitialContextFactory(original, Arrays.asList(
- getURLPreferringDecoratorPicker(), getAccessCheckingDecoratorPicker()));
- }
-
- private static InitialContextFactory getURLPreferringFactory(InitialContextFactory original) {
- return new DecoratingInitialContextFactory(original, Arrays.asList(
- getURLPreferringDecoratorPicker()));
+ ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context,ContextDecorator>>();
+ pickers.add(getURLPreferringDecoratorPicker());
+ pickers.add(getAccessCheckingDecoratorPicker());
+
+ return new DecoratingInitialContextFactory(original, pickers);
}
- private static ContextDecoratorPicker getAccessCheckingDecoratorPicker() {
- ContextDecoratorPicker ret = new ContextDecoratorPicker();
-
- ret.setConstructorParameters(new Object[] { CHECKED_SCHEMES });
- ret.setConstructorParameterTypes(new Class<?>[] { String[].class });
+ private static InitialContextFactory getURLPreferringFactory(InitialContextFactory original) {
+ ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context,ContextDecorator>>();
+ pickers.add(getURLPreferringDecoratorPicker());
- ret.getPossibleDecorators().add(AccessCheckingContextDecorator.class);
- ret.getPossibleDecorators().add(AccessCheckingDirContextDecorator.class);
- ret.getPossibleDecorators().add(AccessCheckingEventContextDecorator.class);
- ret.getPossibleDecorators().add(AccessCheckingEventDirContextDecorator.class);
- ret.getPossibleDecorators().add(AccessCheckingLdapContextDecorator.class);
+ return new DecoratingInitialContextFactory(original, pickers);
+ }
+ private static DecoratorPicker<Context, ContextDecorator> getAccessCheckingDecoratorPicker() {
+ DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>();
+ ret.setContext(new AccessCheckingContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES, CHECKED_SCHEMES));
+
return ret;
}
- private static ContextDecoratorPicker getURLPreferringDecoratorPicker() {
- ContextDecoratorPicker ret = new ContextDecoratorPicker();
-
- ret.setConstructorParameters(null);
- ret.setConstructorParameterTypes(null);
-
- ret.getPossibleDecorators().add(URLPreferringContextDecorator.class);
- ret.getPossibleDecorators().add(URLPreferringDirContextDecorator.class);
- ret.getPossibleDecorators().add(URLPreferringEventContextDecorator.class);
- ret.getPossibleDecorators().add(URLPreferringEventDirContextDecorator.class);
- ret.getPossibleDecorators().add(URLPreferringLdapContextDecorator.class);
-
+ private static DecoratorPicker<Context, ContextDecorator> getURLPreferringDecoratorPicker() {
+ DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>();
+ ret.setContext(new URLPreferringContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES));
+
return ret;
}
}
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java
index 2414c98..19b30ea 100644
--- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java
@@ -19,45 +19,82 @@
package org.rhq.jndi;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
+import java.util.Set;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.spi.InitialContextFactory;
-import org.rhq.jndi.context.ContextDecoratorPicker;
+import org.rhq.jndi.context.ContextDecorator;
+import org.rhq.jndi.util.DecoratorPicker;
+import org.rhq.jndi.util.DecoratingInvocationHandler;
/**
+ * This class implements an initial context factory that decorates the contexts
+ * returned from a "wrapped" initial factory passed to this class in the constructor.
+ * <p>
+ * The contexts returned from the wrapped initial factory are hidden behind a proxy
+ * that implements the intersection of interfaces from the <code>supportedContextInterfaces</code>
+ * constructor parameter and the actual interfaces the wrapped context implements.
+ * <p>
+ * The proxy method calls are handled using the {@link DecoratingInvocationHandler} which is initialized
+ * with the list of {@link DecoratorPicker pickers} that are used to intercept the method
+ * calls on the wrapped context.
+ *
+ * @see DecoratorPicker
+ * @see DecoratingInvocationHandler
*
- *
* @author Lukas Krejci
*/
public class DecoratingInitialContextFactory implements InitialContextFactory {
- private List<ContextDecoratorPicker> pickers;
+ List<DecoratorPicker<Context, ContextDecorator>> pickers;
private InitialContextFactory factory;
+ private Set<Class<? extends Context>> supportedContextInterfaces;
- public DecoratingInitialContextFactory(InitialContextFactory factory, List<ContextDecoratorPicker> decoratorPickers) {
+ public DecoratingInitialContextFactory(InitialContextFactory factory, List<DecoratorPicker<Context, ContextDecorator>> decoratorPickers) {
this.factory = factory;
this.pickers = decoratorPickers;
+ this.supportedContextInterfaces = new HashSet<Class<? extends Context>>();
+ for(DecoratorPicker<Context, ContextDecorator> picker : pickers) {
+ supportedContextInterfaces.addAll(picker.getContext().getSupportedInterfaces());
+ }
}
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
Context ctx = factory.getInitialContext(environment);
- try {
- for(ContextDecoratorPicker picker : pickers) {
- ctx = picker.wrapInAppropriateDecorator(ctx);
- }
- } catch (IllegalArgumentException e) {
- NamingException ex = new NamingException();
- ex.initCause(ex);
-
- throw e;
- }
+ Set<Class<?>> implementedIfaces = getAllImplementedInterfaces(ctx.getClass());
+ Class<?>[] ii = new Class<?>[implementedIfaces.size()];
+ implementedIfaces.toArray(ii);
- return ctx;
+ return (Context) Proxy.newProxyInstance(ctx.getClass().getClassLoader(), ii, new DecoratingInvocationHandler<Context, ContextDecorator>(pickers, ctx));
+ }
+
+ private Set<Class<?>> getAllImplementedInterfaces(Class<?> cls) {
+ HashSet<Class<?>> ret = new HashSet<Class<?>>();
+ getAllImplementedInterfaces(cls, ret);
+
+ ret.retainAll(supportedContextInterfaces);
+
+ return ret;
}
+ private static void getAllImplementedInterfaces(Class<?> cls, Set<Class<?>> output) {
+ Class<?>[] ifaces = cls.getInterfaces();
+
+ for (Class<?> iface : Arrays.asList(ifaces)) {
+ output.add(iface);
+ getAllImplementedInterfaces(iface, output);
+ }
+
+ if (cls.getSuperclass() != null) {
+ getAllImplementedInterfaces(cls.getSuperclass(), output);
+ }
+ }
}
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java
new file mode 100644
index 0000000..284fd0c
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java
@@ -0,0 +1,75 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.context;
+
+import java.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.Context;
+
+import org.rhq.jndi.util.DecoratorSetContext;
+
+/**
+ *
+ *
+ * @author Lukas Krejci
+ */
+public class AccessCheckingContextDecoratorSetContext implements DecoratorSetContext<Context, ContextDecorator> {
+
+ private static final Set<Class<? extends ContextDecorator>> DECORATOR_CLASSES;
+ static {
+ HashSet<Class<? extends ContextDecorator>> tmp = new HashSet<Class<? extends ContextDecorator>>();
+ tmp.add(AccessCheckingContextDecorator.class);
+ tmp.add(AccessCheckingDirContextDecorator.class);
+ tmp.add(AccessCheckingEventContextDecorator.class);
+ tmp.add(AccessCheckingEventDirContextDecorator.class);
+ tmp.add(AccessCheckingLdapContextDecorator.class);
+
+ DECORATOR_CLASSES = Collections.unmodifiableSet(tmp);
+ }
+
+ private Set<Class<? extends Context>> supportedInterfaces;
+ private String[] checkedSchemes;
+
+ public AccessCheckingContextDecoratorSetContext(Set<Class<? extends Context>> supportedInterfaces, String... checkedSchemes) {
+ this.supportedInterfaces = supportedInterfaces;
+ this.checkedSchemes = checkedSchemes;
+ }
+
+ public ContextDecorator instantiate(Class<? extends ContextDecorator> decoratorClass) throws Exception {
+ Constructor<? extends ContextDecorator> ctor = decoratorClass.getConstructor(String[].class);
+
+ return ctor.newInstance((Object)checkedSchemes);
+ }
+
+ public Set<Class<? extends Context>> getSupportedInterfaces() {
+ return supportedInterfaces;
+ }
+
+ public Set<Class<? extends ContextDecorator>> getDecoratorClasses() {
+ return DECORATOR_CLASSES;
+ }
+
+ public void init(ContextDecorator decorator, Context object) throws Exception {
+ decorator.init(object);
+ }
+}
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecoratorPicker.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecoratorPicker.java
deleted file mode 100644
index a088aa2..0000000
--- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecoratorPicker.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * RHQ Management Platform
- * Copyright (C) 2005-2012 Red Hat, Inc.
- * All rights reserved.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package org.rhq.jndi.context;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.naming.Context;
-import javax.naming.NamingException;
-
-/**
- * @author Lukas Krejci
- */
-public class ContextDecoratorPicker {
-
- private Set<Class<? extends ContextDecorator>> possibleDecorators = new HashSet<Class<? extends ContextDecorator>>();
- private Class<?>[] constructorParameterTypes;
- private Object[] constructorParameters;
-
- public ContextDecoratorPicker() {
- }
-
- public ContextDecoratorPicker(Collection<? extends Class<? extends ContextDecorator>> c) {
- possibleDecorators.addAll(c);
- }
-
- public Set<Class<? extends ContextDecorator>> getPossibleDecorators() {
- return possibleDecorators;
- }
-
- public Class<?>[] getConstructorParameterTypes() {
- return constructorParameterTypes;
- }
-
- public void setConstructorParameterTypes(Class<?>[] constructorParameterTypes) {
- this.constructorParameterTypes = constructorParameterTypes;
- }
-
- public Object[] getConstructorParameters() {
- return constructorParameters;
- }
-
- public void setConstructorParameters(Object[] constructorParameters) {
- this.constructorParameters = constructorParameters;
- }
-
- public Context wrapInAppropriateDecorator(Context context) throws NamingException {
- Class<? extends ContextDecorator> cls = getMatchByInterfaces(context, possibleDecorators);
-
- if (cls == null) {
- throw new IllegalArgumentException("Could not find a matching context decorator for " + context.getClass() + " in " + this);
- }
-
- Constructor<? extends ContextDecorator> ctor = null;
- try {
- ctor = cls.getConstructor(constructorParameterTypes);
- ContextDecorator ctx = ctor.newInstance(constructorParameters);
-
- ctx.init(context);
-
- return ctx;
- } catch (SecurityException e) {
- throw new IllegalStateException("Could not instantiate a class through reflection.", e);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(
- "Could not construct a context decorator - unable to find a constructor with parameters "
- + (constructorParameterTypes == null ? "[no parameters]" : Arrays.asList(constructorParameterTypes)) + " on class " + cls.getName(), e);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Could not instantiate a context decorator " + cls + " using constructor " + ctor, e);
- } catch (InstantiationException e) {
- throw new IllegalArgumentException("Could not instantiate a context decorator " + cls + " using constructor " + ctor, e);
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Could not instantiate a context decorator " + cls + " using constructor " + ctor, e);
- } catch (InvocationTargetException e) {
- throw new IllegalArgumentException("Could not instantiate a context decorator " + cls + " using constructor " + ctor, e);
- }
- }
-
- private static <T> Class<? extends T> getMatchByInterfaces(Object obj, Set<Class<? extends T>> classes) {
- Set<Class<?>> ifaces = getAllImplementedInterfaces(obj.getClass());
-
- Class<? extends T> match = null;
- int maxMatchCnt = Integer.MIN_VALUE;
-
- for(Class<? extends T> cls : classes) {
- int cnt = getBestMatchByIfaces(cls, ifaces);
- if (cnt > maxMatchCnt) {
- maxMatchCnt = cnt;
- match = cls;
- }
- }
-
- return match;
- }
-
- private static int getBestMatchByIfaces(Class<?> cls, Set<Class<?>> ifaces) {
- int ret = 0;
-
- //count how many interfaces from the supplied array the class implements
- for(Class<?> iface : ifaces) {
- if (iface.isAssignableFrom(cls)) {
- ++ret;
- } else {
- --ret;
- }
- }
-
- //that's not all - to get the best possible match, we need to take into account
- //the fact that the class might implement more than just the interfaces provided
- Set<Class<?>> clsIfaces = getAllImplementedInterfaces(cls);
-
- for(Class<?> clsIface : clsIfaces) {
- if (!(ifaces.contains(clsIface))) {
- --ret;
- }
- }
-
- return ret;
- }
-
- private static Set<Class<?>> getAllImplementedInterfaces(Class<?> cls) {
- HashSet<Class<?>> ret = new HashSet<Class<?>>();
- getAllImplementedInterfaces(cls, ret);
-
- return ret;
- }
-
- private static void getAllImplementedInterfaces(Class<?> cls, Set<Class<?>> output) {
- Class<?>[] ifaces = cls.getInterfaces();
-
- for(Class<?> iface : Arrays.asList(ifaces)) {
- output.add(iface);
- getAllImplementedInterfaces(iface, output);
- }
-
- if (cls.getSuperclass() != null) {
- getAllImplementedInterfaces(cls.getSuperclass(), output);
- }
- }
-}
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java
new file mode 100644
index 0000000..828cd74
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java
@@ -0,0 +1,71 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.context;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.Context;
+
+import org.rhq.jndi.util.DecoratorSetContext;
+
+/**
+ *
+ *
+ * @author Lukas Krejci
+ */
+public class URLPreferringContextDecoratorSetContext implements DecoratorSetContext<Context, ContextDecorator> {
+
+ private static final Set<Class<? extends ContextDecorator>> DECORATOR_CLASSES;
+ static {
+ HashSet<Class<? extends ContextDecorator>> tmp = new HashSet<Class<? extends ContextDecorator>>();
+ tmp.add(URLPreferringContextDecorator.class);
+ tmp.add(URLPreferringDirContextDecorator.class);
+ tmp.add(URLPreferringEventContextDecorator.class);
+ tmp.add(URLPreferringEventDirContextDecorator.class);
+ tmp.add(URLPreferringLdapContextDecorator.class);
+
+ DECORATOR_CLASSES = Collections.unmodifiableSet(tmp);
+ }
+
+ private Set<Class<? extends Context>> supportedInterfaces;
+
+ public URLPreferringContextDecoratorSetContext(Set<Class<? extends Context>> supportedInterfaces) {
+ this.supportedInterfaces = supportedInterfaces;
+ }
+
+ public Set<Class<? extends Context>> getSupportedInterfaces() {
+ return supportedInterfaces;
+ }
+
+ public Set<Class<? extends ContextDecorator>> getDecoratorClasses() {
+ return DECORATOR_CLASSES;
+ }
+
+ public ContextDecorator instantiate(Class<? extends ContextDecorator> decoratorClass) throws Exception {
+ return decoratorClass.newInstance();
+ }
+
+ public void init(ContextDecorator decorator, Context object) throws Exception {
+ decorator.init(object);
+ }
+
+}
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java
new file mode 100644
index 0000000..356a6e9
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java
@@ -0,0 +1,46 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class DecoratingInvocationHandler<Type, Decorator extends Type> implements InvocationHandler {
+
+ private final List<DecoratorPicker<Type, Decorator>> pickers;
+ private Type object;
+
+ public DecoratingInvocationHandler(List<DecoratorPicker<Type, Decorator>> pickers, Type object) {
+ this.pickers = pickers;
+ this.object = object;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Type target = object;
+ Class<?> methodClass = method.getDeclaringClass();
+
+ for(DecoratorPicker<Type, Decorator> picker : pickers) {
+ target = picker.decorate(target, methodClass);
+ }
+
+ return method.invoke(target, args);
+ }
+}
\ No newline at end of file
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java
new file mode 100644
index 0000000..017d29f
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java
@@ -0,0 +1,195 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.util;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Given as set of decorators extending given type, this class can pick
+ * the most appropriate set of decorators for a class or a method call.
+ * <p>
+ * To configure the decorator, one has to provide a {@link DecoratorSetContext} that
+ * is then used to obtain the list of
+ * {@link DecoratorSetContext#getSupportedInterfaces() supported interfaces}, which are
+ * all the interfaces that should be used for decorator resolution (i.e. all other interfaces
+ * that a class might implement are ignored during decorator resolution), the list of
+ * {@link DecoratorSetContext#getDecoratorClasses() decorator classes}, which is a list
+ * of decorators the picker can choose from and is also used to instantiate and initialize
+ * the decorators.
+ *
+ * @author Lukas Krejci
+ */
+public class DecoratorPicker<Type, Decorator extends Type> {
+
+ private DecoratorSetContext<Type, Decorator> context;
+
+ public DecoratorSetContext<Type, Decorator> getContext() {
+ return context;
+ }
+
+ public void setContext(DecoratorSetContext<Type, Decorator> decoratorSetContext) {
+ this.context = decoratorSetContext;
+ }
+
+ /**
+ * Returns a set of decorators applicable for given method. The set is established based
+ * on the declaring class of the method.
+ *
+ * @param method the method to inspect
+ * @return the set of decorators that can be used to wrap a method call
+ * @throws Exception
+ */
+ public Set<Decorator> getDecoratorsForMethod(Method method) throws Exception {
+ return getDecoratorsForClass_Private(method.getDeclaringClass());
+ }
+
+ /**
+ * Returns a set of decorators that can be used on instances of given class.
+ * @param cls the class to inspect
+ * @return
+ * @throws Exception
+ */
+ public Set<Decorator> getDecoratorsForClass(Class<? extends Type> cls) throws Exception {
+ return getDecoratorsForClass_Private(cls);
+ }
+
+ /**
+ * This method first establishes the set of decorators to use based on the class of the supplied
+ * object and then chains the decorators (in arbitrary order) with the supplied object at the
+ * "root" of the chain.
+ * <p>
+ * If a method is then called on the returned object, the methods of all the decorators are called
+ * in chain (each supposedly calling the next) and finally, at the end of the chain, the method on
+ * the original object (the one supplied to this method) is called.
+ * <p>
+ * Note that the above is only an intended behavior and actually depends on the implementation of
+ * the decorators that are resposinble for the chaining. Each decorator is initialized
+ * (@{link {@link DecoratorSetContext#init(Object, Object)} which should set it up for such chaining.
+ *
+ * @param object
+ * @return
+ * @throws Exception
+ */
+ public Type decorate(Type object) throws Exception {
+ Set<Decorator> decs = getDecoratorsForClass_Private(object.getClass());
+ Type ret = object;
+ for(Decorator d : decs) {
+ context.init(d, ret);
+ ret = d;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Similar to {@link #decorate(Object)} but instead of the class of the object itself,
+ * uses the significantSuperClass as the basis for the decorator resolution.
+ * <p>
+ * This is important, because if the object implements two mutually incompatible sub-interfaces of <code>Type</code>,
+ * the chained decorators might fail to execute a method later on if the decorator depends on the upper part
+ * of the chain to implement certain sub-interface of <code>Type</code>.
+ *
+ * @param object the object to wrap in decorators
+ * @param significantSuperClass the class to base the decorator resolution on
+ * @return
+ * @throws Exception
+ */
+ public Type decorate(Type object, Class<?> significantSuperClass) throws Exception {
+ Set<Decorator> decs = getDecoratorsForClass_Private(significantSuperClass);
+ Type ret = object;
+ for(Decorator d : decs) {
+ context.init(d, ret);
+ ret = d;
+ }
+
+ return ret;
+ }
+
+ private Set<Decorator> getDecoratorsForClass_Private(Class<?> cls) throws Exception {
+ Set<Class<? extends Type>> ifaces = getNearestApplicableInterfaces(cls);
+
+ HashSet<Decorator> ret = new HashSet<Decorator>();
+
+ for (Class<? extends Type> iface : ifaces) {
+ for (Class<? extends Decorator> decClass : getMatch(iface)) {
+ ret.add(context.instantiate(decClass));
+ }
+ }
+
+ return ret;
+ }
+
+ private Set<Class<? extends Type>> getNearestApplicableInterfaces(Class<?> cls) {
+ List<Class<? extends Type>> ifaces = new ArrayList<Class<? extends Type>>(getAllApplicableInterfaces(cls));
+
+ //now compact the set to only contain the most concrete interfaces
+
+ Iterator<Class<? extends Type>> it = ifaces.iterator();
+ while (it.hasNext()) {
+ Class<? extends Type> c = it.next();
+
+ for (int i = 0; i < ifaces.size(); ++i) {
+ Class<? extends Type> nextC = ifaces.get(i);
+ if (!c.equals(nextC) && c.isAssignableFrom(nextC)) {
+ it.remove();
+ break;
+ }
+ }
+ }
+
+ return new HashSet<Class<? extends Type>>(ifaces);
+ }
+
+ private Set<Class<? extends Type>> getAllApplicableInterfaces(Class<?> cls) {
+ Set<Class<? extends Type>> ifaces = new HashSet<Class<? extends Type>>();
+
+ for (Class<? extends Type> iface : context.getSupportedInterfaces()) {
+ if (iface.isAssignableFrom(cls)) {
+ ifaces.add(iface);
+ }
+ }
+
+ if (ifaces.isEmpty()) {
+ throw new IllegalArgumentException("Class " + cls
+ + " doesn't implement any of the applicable interfaces. Cannot find decorators for it.");
+ }
+
+ return ifaces;
+ }
+
+ private Set<Class<? extends Decorator>> getMatch(Class<?> targetIface) {
+
+ Set<Class<? extends Decorator>> ret = new HashSet<Class<? extends Decorator>>();
+
+ for (Class<? extends Decorator> cls : context.getDecoratorClasses()) {
+ if (Arrays.asList(cls.getInterfaces()).contains(targetIface)) {
+ ret.add(cls);
+ }
+ }
+
+ return ret;
+ }
+}
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java
new file mode 100644
index 0000000..d7b1676
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java
@@ -0,0 +1,59 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.util;
+
+import java.util.Set;
+
+/**
+ * Implementations of this interface provide a context to the {@link DecoratorPicker}.
+ *
+ * @author Lukas Krejci
+ */
+public interface DecoratorSetContext<Type, Decorator> {
+
+ /**
+ * @return the set of interfaces that the decorators are able support.
+ * Usually this should be just a union of all interfaces the decorators implement
+ * but can be trimmed down.
+ */
+ Set<Class<? extends Type>> getSupportedInterfaces();
+
+ /**
+ * @return the set of all decorator classes in this set
+ */
+ Set<Class<? extends Decorator>> getDecoratorClasses();
+
+ /**
+ * Instantiates a new decorator of given class.
+ * @param decoratorClass
+ * @return
+ * @throws Exception
+ */
+ Decorator instantiate(Class<? extends Decorator> decoratorClass) throws Exception;
+
+ /**
+ * Initializes the decorator to decorate given object.
+ *
+ * @param decorator
+ * @param object
+ * @throws Exception on error
+ */
+ void init(Decorator decorator, Type object) throws Exception;
+}
diff --git a/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java
new file mode 100644
index 0000000..45ba114
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java
@@ -0,0 +1,145 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.context;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.event.EventContext;
+import javax.naming.event.NamingListener;
+import javax.naming.spi.InitialContextFactory;
+import javax.naming.spi.NamingManager;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder;
+
+/**
+ *
+ *
+ * @author Lukas Krejci
+ */
+@Test
+public class DecoratingInvocationHandlerTest {
+ private static final Set<String> INVOKED_METHODS = new HashSet<String>();
+
+ private static final InvocationHandler NOTE_TAKING_HANDLER = new InvocationHandler() {
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ INVOKED_METHODS.add(method.getName());
+
+ if ("hashCode".equals(method.getName())) {
+ return 0;
+ } else if ("equals".equals(method.getName())) {
+ return false;
+ }
+
+ return null;
+ }
+ };
+
+ private static Class<?>[] CONTEXT_INTERFACES;
+
+ public static class Factory implements InitialContextFactory {
+ public Context getInitialContext(Hashtable<?, ?> environment)
+ throws NamingException {
+
+ return (Context) Proxy.newProxyInstance(DecoratingInvocationHandlerTest.class.getClassLoader(), CONTEXT_INTERFACES, NOTE_TAKING_HANDLER);
+ }
+ }
+
+ private static class DummyInitialEventContext extends InitialContext implements EventContext {
+
+ /**
+ * @param environment
+ * @throws NamingException
+ */
+ public DummyInitialEventContext(Hashtable<?, ?> environment) throws NamingException {
+ super(environment);
+ }
+
+ public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException {
+ ((EventContext)getURLOrDefaultInitCtx(target)).addNamingListener(target, scope, l);
+ }
+
+ public void addNamingListener(String target, int scope, NamingListener l) throws NamingException {
+ ((EventContext)getURLOrDefaultInitCtx(target)).addNamingListener(target, scope, l);
+ }
+
+ public void removeNamingListener(NamingListener l) throws NamingException {
+ ((EventContext)getDefaultInitCtx()).removeNamingListener(l);
+ }
+
+ public boolean targetMustExist() throws NamingException {
+ return ((EventContext)getDefaultInitCtx()).targetMustExist();
+ }
+
+
+ }
+
+ @BeforeClass
+ public void setBuilder() throws Exception {
+ NamingManager.setInitialContextFactoryBuilder(new AccessCheckingInitialContextFactoryBuilder());
+ }
+
+ public void testSimpleDispatch() throws Exception {
+ INVOKED_METHODS.clear();
+ Properties env = new Properties();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, Factory.class.getName());
+
+ CONTEXT_INTERFACES = new Class<?>[] { Context.class };
+
+ InitialContext ctx = new InitialContext(env);
+
+ ctx.lookup("asdf");
+
+ assert INVOKED_METHODS.contains("lookup") : "The lookup doesn't seem to have propagated to the actual context to be used.";
+ }
+
+ public void testMultiInterfaceDispatch() throws Exception {
+ INVOKED_METHODS.clear();
+ Properties env = new Properties();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, Factory.class.getName());
+
+ CONTEXT_INTERFACES = new Class<?>[] { EventContext.class, DirContext.class };
+
+ InitialContext ctx = new InitialContext(env);
+
+ ctx.lookup("asdf");
+
+ DummyInitialEventContext ectx = new DummyInitialEventContext(env);
+
+ ectx.addNamingListener("hodiny", 0, null);
+
+ assert INVOKED_METHODS.contains("lookup") : "The lookup doesn't seem to have propagated to the actual context to be used.";
+ assert INVOKED_METHODS.contains("addNamingListener") : "The addNamingListener doesn't seem to have propagated to the actual context to be used.";
+ }
+}
diff --git a/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java
new file mode 100644
index 0000000..e2d7429
--- /dev/null
+++ b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java
@@ -0,0 +1,180 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2012 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.rhq.jndi.context;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.Context;
+import javax.naming.directory.DirContext;
+import javax.naming.event.EventContext;
+import javax.naming.ldap.LdapContext;
+
+import org.testng.annotations.Test;
+
+import org.rhq.jndi.util.DecoratorPicker;
+import org.rhq.jndi.util.DecoratorSetContext;
+
+/**
+ * @author Lukas Krejci
+ */
+@Test
+public class DecoratorPickerTest {
+
+ private static final InvocationHandler DUMMY_HANDLER = new InvocationHandler() {
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if ("hashCode".equals(method.getName())) {
+ return 0;
+ } else if ("equals".equals(method.getName())) {
+ return false;
+ }
+
+ return null;
+ }
+ };
+
+ private static final Class<?> TEST_OBJECT_CLASS1 = createProxyClass(Context.class);
+ private static final Class<?> TEST_OBJECT_CLASS2 = createProxyClass(DirContext.class);
+ private static final Class<?> TEST_OBJECT_CLASS3 = createProxyClass(LdapContext.class);
+ private static final Class<?> TEST_OBJECT_CLASS4 = createProxyClass(LdapContext.class, EventContext.class);
+ private static final Class<?> DECORATOR_CLASS1 = createProxyClass(Context.class);
+ private static final Class<?> DECORATOR_CLASS2 = createProxyClass(EventContext.class);
+ private static final Class<?> DECORATOR_CLASS3 = createProxyClass(LdapContext.class);
+ private static final Class<?> DECORATOR_CLASS4 = createProxyClass(DirContext.class);
+
+ public void testSimpleDecoratorIdentifiedByClass() throws Exception {
+ DecoratorPicker<Object, Object> picker = createTestPicker();
+
+ Set<Object> contextDecorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS1);
+ assertEquals(contextDecorators.size(), 1, "Expected exactly one decorator for Context class");
+ assertEquals(contextDecorators.iterator().next().getClass().getInterfaces()[0], Context.class);
+ }
+
+ public void testSuperClassDecoratorHasPrecedenceOverSubClassDecorator() throws Exception {
+ DecoratorPicker<Object, Object> picker = createTestPicker();
+
+ //this tests that the LdapContext isn't returned even though it subclasses the DirContext
+ Set<Object> contextDecorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS2);
+ assertEquals(contextDecorators.size(), 1, "Expected exactly one decorator for DirContext class");
+ assertEquals(contextDecorators.iterator().next().getClass().getInterfaces()[0], DirContext.class);
+ }
+
+ public void testSubClassDecoratorCorrectlyIdentified() throws Exception {
+ DecoratorPicker<Object, Object> picker = createTestPicker();
+
+ Set<Object> contextDecorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS3);
+ assertEquals(contextDecorators.size(), 1, "Expected exactly one decorator for LdapContext class");
+ assertEquals(contextDecorators.iterator().next().getClass().getInterfaces()[0], LdapContext.class);
+ }
+
+ public void testMultipleDecoratorsDetectable() throws Exception {
+ DecoratorPicker<Object, Object> picker = createTestPicker();
+
+ Set<Object> decorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS4);
+ assertEquals(decorators.size(), 2,
+ "Exactly 2 decorators should have been found for a class implementing 2 interfaces.");
+
+ boolean ldapContextDecoratorFound = false;
+ boolean eventContextDecoratorFound = false;
+
+ for (Object d : decorators) {
+ if (LdapContext.class.isAssignableFrom(d.getClass())) {
+ ldapContextDecoratorFound = true;
+ continue; //just to make sure that somehow the decorator doesn't implement both
+ }
+ if (EventContext.class.isAssignableFrom(d.getClass())) {
+ eventContextDecoratorFound = true;
+ }
+ }
+
+ assertTrue(ldapContextDecoratorFound && eventContextDecoratorFound,
+ "The found decorators don't implement the desired interfaces.");
+ }
+
+ public void testDecoratorsIdentifiedByMethod() throws Exception {
+ DecoratorPicker<Object, Object> picker = createTestPicker();
+
+ Set<Object> decorators =
+ picker.getDecoratorsForMethod(LdapContext.class.getMethod("getConnectControls", (Class<?>[]) null));
+ assertEquals(decorators.size(), 1,
+ "Expected exactly one decorator for method 'getConnectControls()' from LdapContext class");
+ assertEquals(decorators.iterator().next().getClass().getInterfaces()[0], LdapContext.class);
+ }
+
+ public void testMethodFromSubclassMatchesSubclassDecorator() throws Exception {
+ DecoratorPicker<Object, Object> picker = createTestPicker();
+
+ //this is a method from the DirContext but we're asking for it from a class
+ //that implements also an LdapContext
+ //The LdapContext decorator also inherits from the DirContext decorator
+ //(by the virtue of LdapContext interface inheriting from the DirContext)
+ //The picker should therefore match the LdapContext decorator because its
+ //the "closest" one to the actual class.
+ Set<Object> decorators =
+ picker.getDecoratorsForMethod(TEST_OBJECT_CLASS3.getMethod("getSchemaClassDefinition",
+ new Class<?>[] { String.class }));
+ assertEquals(decorators.size(), 1,
+ "Expected exactly one decorator for method 'getSchemaClassDefinition(String)' from LdapContext class");
+ assertEquals(decorators.iterator().next().getClass().getInterfaces()[0], LdapContext.class);
+ }
+
+ private static Class<?> createProxyClass(Class<?>... ifaces) {
+ return Proxy.getProxyClass(DecoratorPickerTest.class.getClassLoader(), ifaces);
+ }
+
+ private static DecoratorPicker<Object, Object> createTestPicker() {
+ DecoratorPicker<Object, Object> picker = new DecoratorPicker<Object, Object>();
+
+ DecoratorSetContext<Object, Object> decSet = new DecoratorSetContext<Object, Object>() {
+
+ public Object instantiate(Class<? extends Object> decoratorClass) throws Exception {
+ Constructor<? extends Object> ctor = decoratorClass.getConstructor(InvocationHandler.class);
+ return ctor.newInstance(DUMMY_HANDLER);
+ }
+
+ public void init(Object decorator, Object object) throws Exception {
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<Class<? extends Object>> getSupportedInterfaces() {
+ return new HashSet<Class<? extends Object>>(Arrays.asList(Context.class, EventContext.class,
+ LdapContext.class, DirContext.class));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<Class<? extends Object>> getDecoratorClasses() {
+ return new HashSet<Class<? extends Object>>(Arrays.asList(DECORATOR_CLASS1, DECORATOR_CLASS2, DECORATOR_CLASS3, DECORATOR_CLASS4));
+ }
+ };
+
+ picker.setContext(decSet);
+
+ return picker;
+ }
+}