| /* | |
| * 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(); | |
| } | |
| } |