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@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; + } +}
rhq-commits@lists.fedorahosted.org