| /* |
| * Copyright 2010 Google Inc. |
| * |
| * 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 com.google.web.bindery.requestfactory.vm; |
| |
| import com.google.web.bindery.autobean.shared.AutoBean; |
| import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; |
| import com.google.web.bindery.autobean.shared.AutoBeanFactory; |
| import com.google.web.bindery.autobean.vm.impl.BeanMethod; |
| import com.google.web.bindery.autobean.vm.impl.TypeUtils; |
| import com.google.web.bindery.requestfactory.shared.BaseProxy; |
| import com.google.web.bindery.requestfactory.shared.InstanceRequest; |
| import com.google.web.bindery.requestfactory.shared.JsonRpcContent; |
| import com.google.web.bindery.requestfactory.shared.JsonRpcWireName; |
| import com.google.web.bindery.requestfactory.shared.Request; |
| import com.google.web.bindery.requestfactory.shared.RequestContext; |
| import com.google.web.bindery.requestfactory.shared.impl.AbstractRequest; |
| import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext; |
| import com.google.web.bindery.requestfactory.shared.impl.RequestData; |
| import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId; |
| import com.google.web.bindery.requestfactory.vm.impl.Deobfuscator; |
| import com.google.web.bindery.requestfactory.vm.impl.OperationKey; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.lang.reflect.Type; |
| import java.util.Collection; |
| |
| /** |
| * An in-process implementation of RequestContext. |
| */ |
| class InProcessRequestContext extends AbstractRequestContext { |
| class RequestContextHandler implements InvocationHandler { |
| public InProcessRequestContext getContext() { |
| return InProcessRequestContext.this; |
| } |
| |
| public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable { |
| // Maybe delegate to superclass |
| Class<?> owner = method.getDeclaringClass(); |
| if (Object.class.equals(owner) || RequestContext.class.equals(owner) |
| || AbstractRequestContext.class.equals(owner)) { |
| try { |
| return method.invoke(InProcessRequestContext.this, args); |
| } catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| } |
| |
| /* |
| * Instance methods treat the 0-th argument as the instance on which to |
| * invoke the method. |
| */ |
| final Object[] actualArgs; |
| Type returnGenericType; |
| boolean isInstance = InstanceRequest.class.isAssignableFrom(method.getReturnType()); |
| if (isInstance) { |
| returnGenericType = |
| TypeUtils.getParameterization(InstanceRequest.class, method.getGenericReturnType(), |
| method.getReturnType())[1]; |
| if (args == null) { |
| actualArgs = new Object[1]; |
| } else { |
| // Save a slot for the this argument |
| actualArgs = new Object[args.length + 1]; |
| System.arraycopy(args, 0, actualArgs, 1, args.length); |
| } |
| } else { |
| returnGenericType = |
| TypeUtils.getSingleParameterization(Request.class, method.getGenericReturnType(), |
| method.getReturnType()); |
| if (args == null) { |
| actualArgs = NO_ARGS; |
| } else { |
| actualArgs = args; |
| } |
| } |
| |
| Class<?> returnType = TypeUtils.ensureBaseType(returnGenericType); |
| Class<?> elementType = |
| Collection.class.isAssignableFrom(returnType) ? TypeUtils.ensureBaseType(TypeUtils |
| .getSingleParameterization(Collection.class, returnGenericType)) : null; |
| |
| final RequestData data; |
| if (dialect.equals(Dialect.STANDARD)) { |
| StringBuilder descriptor = new StringBuilder("("); |
| for (Class<?> param : method.getParameterTypes()) { |
| appendDescriptor(descriptor, param); |
| } |
| // Don't care about the return type |
| descriptor.append(")V"); |
| OperationKey operation = |
| new OperationKey(context.getName(), method.getName(), descriptor.toString()); |
| |
| data = new RequestData(operation.get(), actualArgs, returnType, elementType); |
| } else { |
| // Calculate request metadata |
| JsonRpcWireName wireInfo = method.getReturnType().getAnnotation(JsonRpcWireName.class); |
| String apiVersion = wireInfo.version(); |
| String operation = wireInfo.value(); |
| |
| int foundContent = -1; |
| final String[] parameterNames = args == null ? new String[0] : new String[args.length]; |
| Annotation[][] parameterAnnotations = method.getParameterAnnotations(); |
| parameter : for (int i = 0, j = parameterAnnotations.length; i < j; i++) { |
| for (Annotation annotation : parameterAnnotations[i]) { |
| if (PropertyName.class.equals(annotation.annotationType())) { |
| parameterNames[i] = ((PropertyName) annotation).value(); |
| continue parameter; |
| } else if (JsonRpcContent.class.equals(annotation.annotationType())) { |
| foundContent = i; |
| continue parameter; |
| } |
| } |
| throw new UnsupportedOperationException("No " + PropertyName.class.getCanonicalName() |
| + " annotation on parameter " + i + " of method " + method.toString()); |
| } |
| final int contentIdx = foundContent; |
| |
| data = new RequestData(operation, actualArgs, returnType, elementType); |
| for (int i = 0, j = parameterNames.length; i < j; i++) { |
| if (i != contentIdx) { |
| data.setNamedParameter(parameterNames[i], args[i]); |
| } else { |
| data.setRequestContent(args[i]); |
| } |
| } |
| data.setApiVersion(apiVersion); |
| } |
| |
| // Create the request, just filling in the RequestData details |
| final AbstractRequest<BaseProxy, Object> req = |
| new AbstractRequest<BaseProxy, Object>(InProcessRequestContext.this) { |
| @Override |
| protected RequestData makeRequestData() { |
| data.setPropertyRefs(propertyRefs); |
| return data; |
| } |
| }; |
| |
| if (!isInstance) { |
| // Instance invocations are enqueued when using() is called |
| addInvocation(req); |
| } |
| |
| if (dialect.equals(Dialect.STANDARD)) { |
| return req; |
| } else if (dialect.equals(Dialect.JSON_RPC)) { |
| // Support optional parameters for JSON-RPC payloads |
| Class<?> requestType = method.getReturnType().asSubclass(Request.class); |
| return Proxy.newProxyInstance(requestType.getClassLoader(), new Class<?>[] {requestType}, |
| new InvocationHandler() { |
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| if (Object.class.equals(method.getDeclaringClass()) |
| || Request.class.equals(method.getDeclaringClass())) { |
| return method.invoke(req, args); |
| } else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) { |
| req.getRequestData().setNamedParameter(BeanMethod.SET.inferName(method), args[0]); |
| return Void.TYPE.equals(method.getReturnType()) ? null : proxy; |
| } |
| throw new UnsupportedOperationException(method.toString()); |
| } |
| }); |
| } else { |
| throw new RuntimeException("Should not reach here"); |
| } |
| } |
| |
| private void appendDescriptor(StringBuilder descriptor, Class<?> param) { |
| // Arrays aren't actually used anywhere in RequestFactory, but it's trivial to |
| // implement and might be useful later on. |
| while (param.isArray()) { |
| descriptor.append('['); |
| param = param.getComponentType(); |
| } |
| |
| if (param.isPrimitive()) { |
| if (param == Boolean.TYPE) { |
| descriptor.append('Z'); |
| } else if (param == Byte.TYPE) { |
| descriptor.append('B'); |
| } else if (param == Character.TYPE) { |
| descriptor.append('C'); |
| } else if (param == Double.TYPE) { |
| descriptor.append('D'); |
| } else if (param == Float.TYPE) { |
| descriptor.append('F'); |
| } else if (param == Integer.TYPE) { |
| descriptor.append('I'); |
| } else if (param == Long.TYPE) { |
| descriptor.append('J'); |
| } else if (param == Short.TYPE) { |
| descriptor.append('S'); |
| } else { |
| assert param == Void.TYPE; |
| descriptor.append('V'); |
| } |
| } else { |
| descriptor.append('L').append(param.getName().replace('.', '/')).append(';'); |
| } |
| } |
| } |
| |
| static final Object[] NO_ARGS = new Object[0]; |
| private final Class<? extends RequestContext> context; |
| private final Deobfuscator deobfuscator; |
| private final Dialect dialect; |
| |
| protected InProcessRequestContext(InProcessRequestFactory factory, Dialect dialect, |
| Class<? extends RequestContext> context) { |
| super(factory, dialect); |
| this.context = context; |
| this.deobfuscator = factory.getDeobfuscator(); |
| this.dialect = dialect; |
| } |
| |
| @Override |
| public <T extends RequestContext> T append(T other) { |
| RequestContextHandler h = (RequestContextHandler) Proxy.getInvocationHandler(other); |
| super.append(h.getContext()); |
| return other; |
| } |
| |
| @Override |
| protected <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz, SimpleProxyId<T> id, |
| boolean useAppendedContexts) { |
| if (deobfuscator.isReferencedType(clazz.getName())) { |
| return super.createProxy(clazz, id, useAppendedContexts); |
| } |
| throw new IllegalArgumentException("Unknown proxy type " + clazz.getName()); |
| } |
| |
| @Override |
| protected AutoBeanFactory getAutoBeanFactory() { |
| return ((InProcessRequestFactory) getRequestFactory()).getAutoBeanFactory(); |
| } |
| } |