/** * Copyright 2005-2016 hdiv.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.hdiv.services; import java.beans.Introspector; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import org.aopalliance.intercept.MethodInterceptor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.target.EmptyTargetSource; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.core.ResolvableType; import org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware; import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation; import org.springframework.objenesis.ObjenesisStd; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; public class Path { public static ThreadLocal<RecordingMethodInterceptor> interceptorThreadLocal = new ThreadLocal<Path.RecordingMethodInterceptor>(); public static <T> T on(final Class<T> type) { return on(type, true); } public static <T> T on(final Class<T> type, final boolean init) { if (init) { interceptorThreadLocal.remove(); interceptorThreadLocal.set(new RecordingMethodInterceptor(type)); } return getProxyWithInterceptor(type, interceptorThreadLocal.get(), type.getClassLoader()); } public static <T> T on(final Class<T> type, final RecordingMethodInterceptor rmi) { return getProxyWithInterceptor(type, rmi, type.getClassLoader()); } @SuppressWarnings("unchecked") public static <T> T collection(final Collection<? extends T> collection) { Assert.isInstanceOf(LastInvocationAware.class, collection); ResolvableType resolvable = ResolvableType.forMethodReturnType(((LastInvocationAware) collection).getLastInvocation().getMethod()); return on((Class<T>) resolvable.getGeneric(0).getRawClass(), false); } @SuppressWarnings("unchecked") public static <T> T collection(final Collection<? extends T> collection, final RecordingMethodInterceptor rmi) { Assert.isInstanceOf(LastInvocationAware.class, collection); ResolvableType resolvable = ResolvableType.forMethodReturnType(((LastInvocationAware) collection).getLastInvocation().getMethod()); return on((Class<T>) resolvable.getGeneric(0).getRawClass(), rmi); } public static String path(final Object obj) { RecordingMethodInterceptor interceptor = interceptorThreadLocal.get(); Assert.notNull(interceptor, "Path.on(Class) should be called first"); interceptorThreadLocal.remove(); return interceptor.getLastInvocation().toString(); } public static class RecordingMethodInterceptor implements MethodInterceptor, LastInvocationAware, org.springframework.cglib.proxy.MethodInterceptor { private static final Method GET_INVOCATIONS; private static final Method GET_OBJECT_PARAMETERS; private final Class<?> targetType; private final Object[] objectParameters; private MethodInvocation invocation; static { GET_INVOCATIONS = ReflectionUtils.findMethod(LastInvocationAware.class, "getLastInvocation"); GET_OBJECT_PARAMETERS = ReflectionUtils.findMethod(LastInvocationAware.class, "getObjectParameters"); } public RecordingMethodInterceptor(final Class<?> targetType, final Object... objectParameters) { this.targetType = targetType; this.objectParameters = objectParameters; } public Iterator<Object> getObjectParameters() { return Arrays.asList(objectParameters).iterator(); } public MethodInvocation getLastInvocation() { return invocation; } public Object invoke(final org.aopalliance.intercept.MethodInvocation invocation) throws Throwable { return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null); } public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy arg3) throws Throwable { if (GET_INVOCATIONS.equals(method)) { return getLastInvocation(); } else if (GET_OBJECT_PARAMETERS.equals(method)) { return getObjectParameters(); } else if (Object.class.equals(method.getDeclaringClass())) { return ReflectionUtils.invokeMethod(method, obj, args); } invocation = new SimpleMethodInvocation(targetType, method, args, getLastInvocation()); Class<?> returnType = method.getReturnType(); if (Modifier.isFinal(returnType.getModifiers())) { return null; } else { return returnType.cast(getProxyWithInterceptor(returnType, this, obj.getClass().getClassLoader())); } } } private static ObjenesisStd OBJENESIS = new ObjenesisStd(); private static <T> T getProxyWithInterceptor(final Class<?> type, final RecordingMethodInterceptor interceptor, final ClassLoader classLoader) { return getProxyWithInterceptor(type, interceptor, classLoader, false); } @SuppressWarnings("unchecked") private static <T> T getProxyWithInterceptor(final Class<?> type, final RecordingMethodInterceptor interceptor, final ClassLoader classLoader, final boolean isfinal) { if (type.isInterface()) { ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE); factory.addInterface(type); factory.addInterface(LastInvocationAware.class); factory.addAdvice(interceptor); return (T) factory.getProxy(); } Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(type); enhancer.setInterfaces(new Class<?>[] { LastInvocationAware.class }); enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); enhancer.setClassLoader(classLoader); Factory factory = (Factory) OBJENESIS.newInstance(enhancer.createClass()); factory.setCallbacks(new Callback[] { interceptor }); return (T) factory; } static class SimpleMethodInvocation implements MethodInvocation { private final Class<?> targetType; private final Method method; private final Object[] arguments; private final MethodInvocation invocation; /** * Creates a new {@link SimpleMethodInvocation} for the given {@link Method} and arguments. * * @param method * @param arguments */ private SimpleMethodInvocation(final Class<?> targetType, final Method method, final Object[] arguments, final MethodInvocation invocation) { this.targetType = targetType; this.arguments = arguments; this.method = method; this.invocation = invocation; } public Class<?> getTargetType() { return targetType; } public Object[] getArguments() { return arguments; } public Method getMethod() { return method; } @Override public String toString() { return (invocation != null ? invocation.toString() + "." : "") + getPropertyFromMethod(method); } private String getPropertyFromMethod(final Method method) { String name = method.getName(); return Introspector.decapitalize(name.substring(name.startsWith("is") ? 2 : 3)); } } public static class PathBuilder { private RecordingMethodInterceptor rmi; public <T> T on(final Class<T> clazz) { rmi = new RecordingMethodInterceptor(clazz); T on = Path.on(clazz, rmi); return on; } public String build(final Object object) { return build(); } public String build() { return rmi.getLastInvocation().toString(); } public <E> E collection(final Collection<? extends E> collection) { return Path.collection(collection, rmi); } } }