/**
  * Returns {@code true} if the type is assignable to EntityProxy or ValueProxy and has a mapping
  * to a domain type.
  *
  * @see
  *     com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator#shouldAttemptProxyValidation()
  */
 private boolean shouldAttemptProxyValidation(JClassType maybeProxy) {
   if (!entityProxyInterface.isAssignableFrom(maybeProxy)
       && !valueProxyInterface.isAssignableFrom(maybeProxy)) {
     return false;
   }
   if (maybeProxy.getAnnotation(ProxyFor.class) == null
       && maybeProxy.getAnnotation(ProxyForName.class) == null) {
     return false;
   }
   return true;
 }
  /** Examine a RequestContext method to see if it returns a transportable type. */
  private boolean validateContextMethodAndSetDataType(
      RequestMethod.Builder methodBuilder, JMethod method, boolean allowSetters)
      throws UnableToCompleteException {
    JClassType requestReturnType = method.getReturnType().isInterface();
    JClassType invocationReturnType;
    if (requestReturnType == null) {
      // Primitive return type
      poison(badContextReturnType(method, requestInterface, instanceRequestInterface));
      return false;
    }

    if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
      // Instance method invocation
      JClassType[] params =
          ModelUtils.findParameterizationOf(instanceRequestInterface, requestReturnType);
      methodBuilder.setInstanceType(getEntityProxyType(params[0]));
      invocationReturnType = params[1];
    } else if (requestInterface.isAssignableFrom(requestReturnType)) {
      // Static method invocation
      JClassType[] params = ModelUtils.findParameterizationOf(requestInterface, requestReturnType);
      invocationReturnType = params[0];

    } else {
      // Unhandled return type, must be something random
      poison(badContextReturnType(method, requestInterface, instanceRequestInterface));
      return false;
    }

    // Validate the parameters
    boolean paramsOk = true;
    JParameter[] params = method.getParameters();
    for (int i = 0; i < params.length; ++i) {
      JParameter param = params[i];
      paramsOk =
          validateTransportableType(new RequestMethod.Builder(), param.getType(), false)
              && paramsOk;
    }

    // Validate any extra properties on the request type
    for (JMethod maybeSetter : requestReturnType.getInheritableMethods()) {
      if (JBeanMethod.SET.matches(maybeSetter) || JBeanMethod.SET_BUILDER.matches(maybeSetter)) {
        if (allowSetters) {
          methodBuilder.addExtraSetter(maybeSetter);
        } else {
          poison(noSettersAllowed(maybeSetter));
        }
      }
    }
    return validateTransportableType(methodBuilder, invocationReturnType, true);
  }
예제 #3
0
  protected String toFormStringExpression(JParameter argument, Style classStyle)
      throws UnableToCompleteException {
    JType type = argument.getType();
    String expr = argument.getName();

    if (type.isPrimitive() != null) {
      return "\"\"+" + expr;
    }
    if (STRING_TYPE == type) {
      return expr;
    }
    if (type.isClass() != null && isOverlayArrayType(type.isClass())) {
      return "(new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString()";
    }
    if (type.isClass() != null && OVERLAY_VALUE_TYPE.isAssignableFrom(type.isClass())) {
      return "(new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString()";
    }
    if (type.getQualifiedBinaryName().startsWith("java.lang.")) {
      return String.format("(%s != null ? %s.toString() : null)", expr, expr);
    }

    Json jsonAnnotation = argument.getAnnotation(Json.class);
    final Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;

    return locator.encodeExpression(type, expr, style) + ".toString()";
  }
예제 #4
0
  private void writeSubresourceLocatorImpl(JMethod method) throws UnableToCompleteException {
    JClassType iface = method.getReturnType().isInterface();
    if (iface == null || !REST_SERVICE_TYPE.isAssignableFrom(iface)) {
      getLogger()
          .log(
              ERROR,
              "Invalid subresource locator method. Method must have return type of an interface that extends RestService: "
                  + method.getReadableDeclaration());
      throw new UnableToCompleteException();
    }

    Path pathAnnotation = method.getAnnotation(Path.class);
    if (pathAnnotation == null) {
      getLogger()
          .log(
              ERROR,
              "Invalid subresource locator method. Method must have @Path annotation: "
                  + method.getReadableDeclaration());
      throw new UnableToCompleteException();
    }
    String pathExpression = wrap(pathAnnotation.value());

    for (JParameter arg : method.getParameters()) {
      PathParam paramPath = arg.getAnnotation(PathParam.class);
      if (paramPath != null) {
        pathExpression = pathExpression(pathExpression, arg, paramPath);
      }
    }

    p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
    {
      JType type = method.getReturnType();
      String name;
      if (type instanceof JClassType) {
        JClassType restService = (JClassType) type;
        RestServiceClassCreator generator =
            new RestServiceClassCreator(getLogger(), context, restService);
        name = generator.create();
      } else {
        throw new UnsupportedOperationException("Subresource method may not return: " + type);
      }
      p(method.getReturnType().getQualifiedSourceName() + " __subresource = new " + name + "();");
      p(
          "(("
              + RestServiceProxy.class.getName()
              + ")__subresource).setResource(getResource().resolve("
              + pathExpression
              + "));");
      p("return __subresource;");
    }
    i(-1).p("}");
  }
  public RequestFactoryModel(TreeLogger logger, JClassType factoryType)
      throws UnableToCompleteException {
    this.logger = logger;
    this.factoryType = factoryType;
    this.oracle = factoryType.getOracle();
    collectionInterface = oracle.findType(Collection.class.getCanonicalName());
    entityProxyInterface = oracle.findType(EntityProxy.class.getCanonicalName());
    instanceRequestInterface = oracle.findType(InstanceRequest.class.getCanonicalName());
    listInterface = oracle.findType(List.class.getCanonicalName());
    mapInterface = oracle.findType(Map.class.getCanonicalName());
    requestContextInterface = oracle.findType(RequestContext.class.getCanonicalName());
    requestFactoryInterface = oracle.findType(RequestFactory.class.getCanonicalName());
    requestInterface = oracle.findType(Request.class.getCanonicalName());
    setInterface = oracle.findType(Set.class.getCanonicalName());
    splittableType = oracle.findType(Splittable.class.getCanonicalName());
    valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());

    extraTypes = checkExtraTypes(factoryType, false);
    for (JMethod method : factoryType.getOverridableMethods()) {
      if (method.getEnclosingType().equals(requestFactoryInterface)) {
        // Ignore methods defined an RequestFactory itself
        continue;
      }

      if (method.getParameters().length > 0) {
        poison("Unexpected parameter on method %s", method.getName());
        continue;
      }

      JClassType contextType = method.getReturnType().isInterface();
      if (contextType == null || !requestContextInterface.isAssignableFrom(contextType)) {
        poison(
            "Unexpected return type %s on method %s is not" + " an interface assignable to %s",
            method.getReturnType().getQualifiedSourceName(),
            method.getName(),
            requestContextInterface.getSimpleSourceName());
        continue;
      }

      ContextMethod.Builder builder = new ContextMethod.Builder();
      builder.setDeclaredMethod(method);
      buildContextMethod(builder, contextType);
      contextMethods.add(builder.build());
    }

    if (poisoned) {
      die(poisonedMessage());
    }
  }
예제 #6
0
  protected String toStringExpression(JType type, String expr) {
    if (type.isPrimitive() != null) {
      return "\"\"+" + expr;
    }
    if (STRING_TYPE == type) {
      return expr;
    }
    if (type.isClass() != null && isOverlayArrayType(type.isClass())) {
      return "(new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString()";
    }
    if (type.isClass() != null && OVERLAY_VALUE_TYPE.isAssignableFrom(type.isClass())) {
      return "(new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString()";
    }

    return String.format("(%s != null ? %s.toString() : null)", expr, expr);
  }
  public static JMethod getInstantiationMethod(JClassType serializer, JClassType serializee) {
    JMethod[] overloads = serializer.getOverloads("instantiate");
    for (JMethod overload : overloads) {
      JParameter[] parameters = overload.getParameters();

      if (parameters.length != 1) {
        // Different overload
        continue;
      }

      if (!parameters[0]
          .getType()
          .getQualifiedSourceName()
          .equals(SerializationStreamReader.class.getName())) {
        // First param is not a stream class
        continue;
      }

      if (!isValidCustomFieldSerializerMethod(overload)) {
        continue;
      }

      JType type = overload.getReturnType();
      if (type.isPrimitive() != null) {
        // Primitives are auto serialized so this can't be the right method
        continue;
      }

      // TODO: if isArray answered yes to isClass this cast would not be
      // necessary
      JClassType clazz = (JClassType) type;
      if (clazz.isAssignableFrom(serializee)) {
        return overload;
      }
    }

    return null;
  }
  private static JMethod getMethod(
      String methodName, String streamClassName, JClassType serializer, JClassType serializee) {
    JMethod[] overloads = serializer.getOverloads(methodName);
    for (JMethod overload : overloads) {
      JParameter[] parameters = overload.getParameters();

      if (parameters.length != 2) {
        // Different overload
        continue;
      }

      if (!parameters[0].getType().getQualifiedSourceName().equals(streamClassName)) {
        // First param is not a stream class
        continue;
      }

      JParameter serializeeParam = parameters[1];
      JType type = serializeeParam.getType();
      if (type.isPrimitive() != null) {
        // Primitives are auto serialized so this can't be the right method
        continue;
      }

      // TODO: if isArray answered yes to isClass this cast would not be
      // necessary
      JClassType clazz = (JClassType) type;
      if (clazz.isAssignableFrom(serializee)) {
        if (isValidCustomFieldSerializerMethod(overload)
            && overload.getReturnType() == JPrimitiveType.VOID) {
          return overload;
        }
      }
    }

    return null;
  }
예제 #9
0
  @Override
  protected void generate() throws UnableToCompleteException {

    if (source.isInterface() == null) {
      getLogger().log(ERROR, "Type is not an interface.");
      throw new UnableToCompleteException();
    }

    locator = new JsonEncoderDecoderInstanceLocator(context, getLogger());

    this.XML_CALLBACK_TYPE = find(XmlCallback.class, getLogger(), context);
    this.METHOD_CALLBACK_TYPE = find(MethodCallback.class, getLogger(), context);
    this.TEXT_CALLBACK_TYPE = find(TextCallback.class, getLogger(), context);
    this.JSON_CALLBACK_TYPE = find(JsonCallback.class, getLogger(), context);
    this.OVERLAY_CALLBACK_TYPE = find(OverlayCallback.class, getLogger(), context);
    this.DOCUMENT_TYPE = find(Document.class, getLogger(), context);
    this.METHOD_TYPE = find(Method.class, getLogger(), context);
    this.STRING_TYPE = find(String.class, getLogger(), context);
    this.JSON_VALUE_TYPE = find(JSONValue.class, getLogger(), context);
    this.OVERLAY_VALUE_TYPE = find(JavaScriptObject.class, getLogger(), context);
    this.OVERLAY_ARRAY_TYPES = new HashSet<JClassType>();
    this.OVERLAY_ARRAY_TYPES.add(find(JsArray.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayBoolean.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayInteger.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayNumber.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayString.class, getLogger(), context));
    this.QUERY_PARAM_LIST_TYPES = new HashSet<JClassType>();
    this.QUERY_PARAM_LIST_TYPES.add(find(List.class, getLogger(), context));
    this.QUERY_PARAM_LIST_TYPES.add(find(Set.class, getLogger(), context));
    this.REST_SERVICE_TYPE = find(RestService.class, getLogger(), context);

    String path = null;
    Path pathAnnotation = source.getAnnotation(Path.class);
    if (pathAnnotation != null) {
      path = pathAnnotation.value();
    }

    RemoteServiceRelativePath relativePath = source.getAnnotation(RemoteServiceRelativePath.class);
    if (relativePath != null) {
      path = relativePath.value();
    }

    p("private " + RESOURCE_CLASS + " resource = null;");
    p();

    p("public void setResource(" + RESOURCE_CLASS + " resource) {").i(1);
    {
      p("this.resource = resource;");
    }
    i(-1).p("}");

    p("public " + RESOURCE_CLASS + " getResource() {").i(1);
    {
      p("if (this.resource == null) {").i(1);
      if (path == null) {
        p("this.resource = new " + RESOURCE_CLASS + "(" + DEFAULTS_CLASS + ".getServiceRoot());");
      } else {
        p(
            "this.resource = new "
                + RESOURCE_CLASS
                + "("
                + DEFAULTS_CLASS
                + ".getServiceRoot()).resolve("
                + quote(path)
                + ");");
      }
      i(-1).p("}");
      p("return this.resource;");
    }
    i(-1).p("}");

    Options options = source.getAnnotation(Options.class);
    if (options != null && options.dispatcher() != Dispatcher.class) {
      p(
          "private "
              + DISPATCHER_CLASS
              + " dispatcher = "
              + options.dispatcher().getName()
              + ".INSTANCE;");
    } else {
      p("private " + DISPATCHER_CLASS + " dispatcher = null;");
    }

    p();
    p("public void setDispatcher(" + DISPATCHER_CLASS + " dispatcher) {").i(1);
    {
      p("this.dispatcher = dispatcher;");
    }
    i(-1).p("}");

    p();
    p("public " + DISPATCHER_CLASS + " getDispatcher() {").i(1);
    {
      p("return this.dispatcher;");
    }
    i(-1).p("}");

    for (JMethod method : source.getInheritableMethods()) {
      JClassType iface = method.getReturnType().isInterface();
      if (iface != null && REST_SERVICE_TYPE.isAssignableFrom(iface))
        writeSubresourceLocatorImpl(method);
      else writeMethodImpl(method);
    }
  }
예제 #10
0
  /** Adds a root type for each type that appears in the RemoteService interface methods. */
  private static void addRemoteServiceRootTypes(
      TreeLogger logger,
      TypeOracle typeOracle,
      SerializableTypeOracleBuilder typesSentFromBrowser,
      SerializableTypeOracleBuilder typesSentToBrowser,
      JClassType remoteService)
      throws NotFoundException, UnableToCompleteException {
    logger =
        logger.branch(
            TreeLogger.DEBUG,
            "Analyzing '"
                + remoteService.getParameterizedQualifiedSourceName()
                + "' for serializable types",
            null);

    JMethod[] methods = remoteService.getOverridableMethods();

    JClassType exceptionClass = typeOracle.getType(Exception.class.getName());

    JClassType rteType = typeOracle.getType(RpcTokenException.class.getName());
    JClassType rpcTokenClass = typeOracle.getType(RpcToken.class.getName());
    RpcTokenImplementation tokenClassToUse =
        remoteService.findAnnotationInTypeHierarchy(RpcTokenImplementation.class);
    if (tokenClassToUse != null) {
      // only include serializer for the specified class literal
      JClassType rpcTokenType = typeOracle.getType(tokenClassToUse.value());
      if (rpcTokenType.isAssignableTo(rpcTokenClass)) {
        typesSentFromBrowser.addRootType(logger, rpcTokenType);
        typesSentToBrowser.addRootType(logger, rteType);
      } else {
        logger.branch(
            TreeLogger.ERROR,
            "RPC token class "
                + tokenClassToUse.value()
                + " must implement "
                + RpcToken.class.getName(),
            null);
        throw new UnableToCompleteException();
      }
    } else {
      JClassType[] rpcTokenSubclasses = rpcTokenClass.getSubtypes();
      for (JClassType rpcTokenSubclass : rpcTokenSubclasses) {
        typesSentFromBrowser.addRootType(logger, rpcTokenSubclass);
      }
      if (rpcTokenSubclasses.length > 0) {
        typesSentToBrowser.addRootType(logger, rteType);
      }
    }

    TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG, "Analyzing methods:", null);
    for (JMethod method : methods) {
      TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG, method.toString(), null);
      JType returnType = method.getReturnType();
      if (returnType != JPrimitiveType.VOID) {
        TreeLogger returnTypeLogger =
            methodLogger.branch(
                TreeLogger.DEBUG,
                "Return type: " + returnType.getParameterizedQualifiedSourceName(),
                null);
        typesSentToBrowser.addRootType(returnTypeLogger, returnType);
      }

      JParameter[] params = method.getParameters();
      for (JParameter param : params) {
        TreeLogger paramLogger =
            methodLogger.branch(TreeLogger.DEBUG, "Parameter: " + param.toString(), null);
        JType paramType = param.getType();
        typesSentFromBrowser.addRootType(paramLogger, paramType);
      }

      JType[] exs = method.getThrows();
      if (exs.length > 0) {
        TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG, "Throws:", null);
        for (JType ex : exs) {
          if (!exceptionClass.isAssignableFrom(ex.isClass())) {
            throwsLogger =
                throwsLogger.branch(
                    TreeLogger.WARN,
                    "'"
                        + ex.getQualifiedSourceName()
                        + "' is not a checked exception; only checked exceptions may be used",
                    null);
          }

          typesSentToBrowser.addRootType(throwsLogger, ex);
        }
      }
    }
  }
  /** Examines a type to see if it can be transported. */
  private boolean validateTransportableType(
      RequestMethod.Builder methodBuilder, JType type, boolean requireObject)
      throws UnableToCompleteException {
    JClassType transportedClass = type.isClassOrInterface();
    if (transportedClass == null) {
      if (requireObject) {
        poison(
            "The type %s cannot be transported by RequestFactory as" + " a return type",
            type.getQualifiedSourceName());
        return false;
      } else {
        // Primitives always ok
        return true;
      }
    }

    if (ModelUtils.isValueType(oracle, transportedClass)
        || splittableType.equals(transportedClass)) {
      // Simple values, like Integer and String
      methodBuilder.setValueType(true);
    } else if (entityProxyInterface.isAssignableFrom(transportedClass)
        || valueProxyInterface.isAssignableFrom(transportedClass)) {
      // EntityProxy and ValueProxy return types
      methodBuilder.setEntityType(getEntityProxyType(transportedClass));
    } else if (collectionInterface.isAssignableFrom(transportedClass)) {
      // Only allow certain collections for now
      JParameterizedType parameterized = transportedClass.isParameterized();
      if (parameterized == null) {
        poison("Requests that return collections of List or Set must be parameterized");
        return false;
      }
      if (listInterface.equals(parameterized.getBaseType())) {
        methodBuilder.setCollectionType(CollectionType.LIST);
      } else if (setInterface.equals(parameterized.getBaseType())) {
        methodBuilder.setCollectionType(CollectionType.SET);
      } else {
        poison(
            "Requests that return collections may be declared with" + " %s or %s only",
            listInterface.getQualifiedSourceName(), setInterface.getQualifiedSourceName());
        return false;
      }
      // Also record the element type in the method builder
      JClassType elementType =
          ModelUtils.findParameterizationOf(collectionInterface, transportedClass)[0];
      methodBuilder.setCollectionElementType(elementType);
      validateTransportableType(methodBuilder, elementType, requireObject);
    } else if (mapInterface.isAssignableFrom(transportedClass)) {
      JParameterizedType parameterized = transportedClass.isParameterized();
      if (parameterized == null) {
        poison("Requests that return Maps must be parameterized");
        return false;
      }
      if (mapInterface.equals(parameterized.getBaseType())) {
        methodBuilder.setCollectionType(CollectionType.MAP);
      } else {
        poison(
            "Requests that return maps may be declared with" + " %s only",
            mapInterface.getQualifiedSourceName());
        return false;
      }
      // Also record the element type in the method builder
      JClassType[] params = ModelUtils.findParameterizationOf(mapInterface, transportedClass);
      JClassType keyType = params[0];
      JClassType valueType = params[1];
      methodBuilder.setMapKeyType(keyType);
      methodBuilder.setMapValueType(valueType);
      validateTransportableType(methodBuilder, keyType, requireObject);
      validateTransportableType(methodBuilder, valueType, requireObject);
    } else {
      // Unknown type, fail
      poison("Invalid Request parameterization %s", transportedClass.getQualifiedSourceName());
      return false;
    }
    methodBuilder.setDataType(transportedClass);
    return true;
  }
  private EntityProxyModel getEntityProxyType(JClassType entityProxyType)
      throws UnableToCompleteException {
    entityProxyType = ModelUtils.ensureBaseType(entityProxyType);
    EntityProxyModel toReturn = peers.get(entityProxyType);
    if (toReturn == null) {
      EntityProxyModel.Builder inProgress = peerBuilders.get(entityProxyType);
      if (inProgress != null) {
        toReturn = inProgress.peek();
      }
    }
    if (toReturn == null) {
      EntityProxyModel.Builder builder = new EntityProxyModel.Builder();
      peerBuilders.put(entityProxyType, builder);

      // Validate possible super-proxy types first
      for (JClassType supertype : entityProxyType.getFlattenedSupertypeHierarchy()) {
        List<EntityProxyModel> superTypes = new ArrayList<EntityProxyModel>();
        if (supertype != entityProxyType && shouldAttemptProxyValidation(supertype)) {
          superTypes.add(getEntityProxyType(supertype));
        }
        builder.setSuperProxyTypes(superTypes);
      }

      builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType));
      builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType));
      if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
        builder.setType(Type.ENTITY);
      } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
        builder.setType(Type.VALUE);
      } else {
        poison(
            "The type %s is not assignable to either %s or %s",
            entityProxyInterface.getQualifiedSourceName(),
            valueProxyInterface.getQualifiedSourceName());
        // Cannot continue, since knowing the behavior is crucial
        die(poisonedMessage());
      }

      // Get the server domain object type
      ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
      ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class);
      JsonRpcProxy jsonRpcProxy = entityProxyType.getAnnotation(JsonRpcProxy.class);
      if (proxyFor == null && proxyForName == null && jsonRpcProxy == null) {
        poison(
            "The %s type does not have a @%s, @%s, or @%s annotation",
            entityProxyType.getQualifiedSourceName(),
            ProxyFor.class.getSimpleName(),
            ProxyForName.class.getSimpleName(),
            JsonRpcProxy.class.getSimpleName());
      }

      // Look at the methods declared on the EntityProxy
      List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
      Map<String, JMethod> duplicatePropertyGetters = new HashMap<String, JMethod>();
      for (JMethod method : entityProxyType.getInheritableMethods()) {
        if (method.getEnclosingType().equals(entityProxyInterface)) {
          // Ignore methods on EntityProxy
          continue;
        }
        RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
        methodBuilder.setDeclarationMethod(entityProxyType, method);

        JType transportedType;
        String name = method.getName();
        if (JBeanMethod.GET.matches(method)) {
          transportedType = method.getReturnType();
          String propertyName = JBeanMethod.GET.inferName(method);
          JMethod previouslySeen = duplicatePropertyGetters.get(propertyName);
          if (previouslySeen == null) {
            duplicatePropertyGetters.put(propertyName, method);
          } else {
            poison(
                "Duplicate accessors for property %s: %s() and %s()",
                propertyName, previouslySeen.getName(), method.getName());
          }

        } else if (JBeanMethod.SET.matches(method) || JBeanMethod.SET_BUILDER.matches(method)) {
          transportedType = method.getParameters()[0].getType();

        } else if (name.equals("stableId") && method.getParameters().length == 0) {
          // Ignore any overload of stableId
          continue;
        } else {
          poison("The method %s is neither a getter nor a setter", method.getReadableDeclaration());
          continue;
        }
        validateTransportableType(methodBuilder, transportedType, false);
        RequestMethod requestMethod = methodBuilder.build();
        requestMethods.add(requestMethod);
      }
      builder
          .setExtraTypes(checkExtraTypes(entityProxyType, false))
          .setRequestMethods(requestMethods);

      toReturn = builder.build();
      peers.put(entityProxyType, toReturn);
      peerBuilders.remove(entityProxyType);
    }
    return toReturn;
  }