/******************************************************************************* * Copyright (c) 2008, 2014 Stuart McCulloch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stuart McCulloch - initial API and implementation *******************************************************************************/ package org.eclipse.sisu.peaberry.util.decorators; import java.lang.reflect.AccessibleObject; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.eclipse.sisu.peaberry.Import; import org.eclipse.sisu.peaberry.ServiceUnavailableException; import org.eclipse.sisu.peaberry.builders.ImportDecorator; import org.eclipse.sisu.peaberry.util.DelegatingImport; import com.google.inject.matcher.Matcher; /** * An {@link ImportDecorator} that supports {@link MethodInterceptor}s. * * @author mcculls@gmail.com (Stuart McCulloch) */ public final class InterceptingDecorator<S> implements ImportDecorator<S> { final Matcher<? super Class<?>> classMatcher; final Matcher<? super Method> methodMatcher; final MethodInterceptor[] interceptors; public InterceptingDecorator(final Matcher<? super Class<?>> classMatcher, final Matcher<? super Method> methodMatcher, final MethodInterceptor... interceptors) { this.classMatcher = classMatcher; this.methodMatcher = methodMatcher; this.interceptors = interceptors; if (interceptors.length == 0) { throw new IllegalArgumentException("Must provide at least one method interceptor"); } } // use JDK proxy for simplicity private final class ProxyImport<T> extends DelegatingImport<T> implements InvocationHandler { private volatile T proxy; ProxyImport(final Import<T> service) { super(service); } @Override @SuppressWarnings("unchecked") public T get() { if (null == proxy) { synchronized (this) { try { final T instance = super.get(); if (null == proxy && null != instance) { // lazily-create proxy, only needs to be created once per service final ClassLoader loader = interceptors[0].getClass().getClassLoader(); proxy = (T) Proxy.newProxyInstance(loader, getInterfaces(instance), this); } } finally { super.unget(); } } } return proxy; // proxy will use get() to delegate to the active service } @Override public void unget() {/* proxy does the cleanup */} public Object invoke(final Object unused, final Method method, final Object[] args) throws Throwable { try { final Object instance = super.get(); if (null == instance) { throw new ServiceUnavailableException(); } // only intercept interesting methods if (!methodMatcher.matches(method) || !classMatcher.matches(method.getDeclaringClass())) { return method.invoke(instance, args); } return intercept(instance, method, args); } finally { super.unget(); } } private Object intercept(final Object instance, final Method method, final Object[] args) throws Throwable { // begin chain of intercepting method invocations return interceptors[0].invoke(new MethodInvocation() { private int index = 1; public AccessibleObject getStaticPart() { return method; } public Method getMethod() { return method; } public Object[] getArguments() { return args; } public Object getThis() { return instance; } public Object proceed() throws Throwable { try { // walk down the stack of interceptors if (index < interceptors.length) { return interceptors[index++].invoke(this); } // no more interceptors, invoke service return method.invoke(instance, args); } finally { index--; // rollback in case called again } } }); } } static Class<?>[] getInterfaces(final Object instance) { @SuppressWarnings("unchecked") final Set<Class> api = new HashSet<Class>(); // look through the entire class hierarchy collecting all visible interfaces for (Class<?> clazz = instance.getClass(); null != clazz; clazz = clazz.getSuperclass()) { Collections.addAll(api, clazz.getInterfaces()); } return api.toArray(new Class[api.size()]); } public <T extends S> Import<T> decorate(final Import<T> service) { return new ProxyImport<T>(service); } }