@Override
  @SuppressWarnings("unchecked")
  public PartialRestResponse buildResponse(
      RoutingResult routingResult, RestLiResponseData responseData) {
    List<CreateCollectionResponseEnvelope.CollectionCreateResponseItem> collectionCreateResponses =
        responseData.getCreateCollectionResponseEnvelope().getCreateResponses();
    List<CreateIdStatus<Object>> formattedResponses =
        new ArrayList<CreateIdStatus<Object>>(collectionCreateResponses.size());

    // Iterate through the responses and generate the ErrorResponse with the appropriate override
    // for exceptions.
    // Otherwise, add the result as is.
    for (CreateCollectionResponseEnvelope.CollectionCreateResponseItem response :
        collectionCreateResponses) {
      if (response.isErrorResponse()) {
        RestLiServiceException exception = response.getException();
        formattedResponses.add(
            new CreateIdStatus<Object>(
                exception.getStatus().getCode(),
                response.getId(),
                _errorResponseBuilder.buildErrorResponse(exception),
                ProtocolVersionUtil.extractProtocolVersion(responseData.getHeaders())));
      } else {
        if (response.getRecord() instanceof CreateIdEntityStatus) {
          CreateIdEntityStatus<?, ?> currentStatus =
              (CreateIdEntityStatus<?, ?>) response.getRecord();
          final ResourceContext resourceContext = routingResult.getContext();
          DataMap entityData =
              currentStatus.getEntity() != null ? currentStatus.getEntity().data() : null;
          final DataMap data =
              RestUtils.projectFields(
                  entityData,
                  resourceContext.getProjectionMode(),
                  resourceContext.getProjectionMask());
          CreateIdEntityStatus<?, ?> projectedStatus =
              new CreateIdEntityStatus(
                  currentStatus.getStatus(),
                  currentStatus.getKey(),
                  new AnyRecord(data),
                  currentStatus.getError(),
                  ProtocolVersionUtil.extractProtocolVersion(resourceContext.getRequestHeaders()));
          formattedResponses.add((CreateIdStatus<Object>) projectedStatus);
        } else {
          formattedResponses.add((CreateIdStatus<Object>) response.getRecord());
        }
      }
    }

    PartialRestResponse.Builder builder = new PartialRestResponse.Builder();
    BatchCreateIdResponse<Object> batchCreateIdResponse =
        new BatchCreateIdResponse<Object>(formattedResponses);
    return builder
        .headers(responseData.getHeaders())
        .cookies(responseData.getCookies())
        .entity(batchCreateIdResponse)
        .build();
  }
  private RestResponseBuilder encodeResult(
      RestResponseBuilder builder, DataMap dataMap, String acceptTypes) {
    String bestType = RestUtils.pickBestEncoding(acceptTypes);

    if (RestConstants.HEADER_VALUE_APPLICATION_PSON.equalsIgnoreCase(bestType)) {
      builder.setHeader(
          RestConstants.HEADER_CONTENT_TYPE, RestConstants.HEADER_VALUE_APPLICATION_PSON);
      builder.setEntity(DataMapUtils.mapToPsonBytes(dataMap));
    } else if (RestConstants.HEADER_VALUE_APPLICATION_JSON.equalsIgnoreCase(bestType)) {
      builder.setHeader(
          RestConstants.HEADER_CONTENT_TYPE, RestConstants.HEADER_VALUE_APPLICATION_JSON);
      builder.setEntity(DataMapUtils.mapToBytes(dataMap, _permissiveEncoding));
    } else {
      throw new RoutingException(
          "No acceptable types can be returned", HttpStatus.S_406_NOT_ACCEPTABLE.getCode());
    }
    return builder;
  }
Example #3
0
  /**
   * Invokes the method with the specified callback and arguments built from the request.
   *
   * @param invocableMethod {@link RoutingResult}
   * @param request {@link RestRequest}
   * @param callback {@link RestLiCallback}
   * @param isDebugMode whether the invocation will be done as part of a debug request.
   * @param filterContext {@link FilterRequestContextInternal}
   */
  public void invoke(
      final RoutingResult invocableMethod,
      final RestRequest request,
      final RequestExecutionCallback<Object> callback,
      final boolean isDebugMode,
      final FilterRequestContextInternal filterContext) {
    RequestExecutionReportBuilder requestExecutionReportBuilder = null;

    if (isDebugMode) {
      requestExecutionReportBuilder = new RequestExecutionReportBuilder();
    }

    // Fast fail if the request headers are invalid.
    try {
      RestUtils.validateRequestHeadersAndUpdateResourceContext(
          request.getHeaders(), (ServerResourceContext) invocableMethod.getContext());
    } catch (RestLiServiceException e) {
      callback.onError(e, getRequestExecutionReport(requestExecutionReportBuilder));
      return;
    }
    // Request headers are valid. Proceed with the invocation of the filters and eventually the
    // resource.
    ResourceMethodDescriptor resourceMethodDescriptor = invocableMethod.getResourceMethod();

    RestLiArgumentBuilder adapter =
        _methodAdapterRegistry.getArgumentBuilder(resourceMethodDescriptor.getType());
    if (adapter == null) {
      throw new IllegalArgumentException(
          "Unsupported method type: " + resourceMethodDescriptor.getType());
    }
    RestLiRequestData requestData = adapter.extractRequestData(invocableMethod, request);
    filterContext.setRequestData(requestData);
    // Kick off the request filter iterator, which finally would invoke the resource.
    RestLiRequestFilterChainCallback restLiRequestFilterChainCallback =
        new RestLiRequestFilterChainCallbackImpl(
            invocableMethod, adapter, callback, requestExecutionReportBuilder);
    new RestLiRequestFilterChain(_requestFilters, restLiRequestFilterChainCallback)
        .onRequest(filterContext);
  }
Example #4
0
  /**
   * This method recursively removes all values from a RecordTemplate that do not match some field
   * in the schema via an all positive projection generated from the schema unless whitelisted by
   * the override parameter.
   *
   * <p>RecordTemplates with readonly datamaps should not invoke this. An exception will be thrown
   * if this is done.
   *
   * @param recordTemplate represents the RecordTemplate that will be trimmed.
   * @param override represents the MaskTree that defines how fields outside the schema should be
   *     preserved.
   * @param failOnMismatch true if an exception should be thrown if there is a data-schema mismatch.
   */
  public static void trimRecordTemplate(
      RecordTemplate recordTemplate, MaskTree override, final boolean failOnMismatch) {
    if (recordTemplate.schema() == null) {
      return;
    }

    DataMap overrideResults =
        RestUtils.projectFields(recordTemplate.data(), ProjectionMode.AUTOMATIC, override);
    Builder.create(recordTemplate.data(), recordTemplate.schema(), IterationOrder.PRE_ORDER)
        .filterBy(
            new Predicate() {
              @Override
              public boolean evaluate(DataElement element) {
                if (failOnMismatch && element.getSchema() == null) {
                  throw new IllegalArgumentException();
                }

                return element.getSchema() == null;
              }
            })
        .remove();

    CheckedUtil.putAllWithoutChecking(recordTemplate.data(), overrideResults);
  }
Example #5
0
  /**
   * Build arguments for resource method invocation. Combines various types of arguments into a
   * single array.
   *
   * @param positionalArguments pass-through arguments coming from {@link RestLiArgumentBuilder}
   * @param resourceMethod the resource method
   * @param context {@link ResourceContext}
   * @param template {@link DynamicRecordTemplate}
   * @return array of method argument for method invocation.
   */
  @SuppressWarnings("deprecation")
  public static Object[] buildArgs(
      final Object[] positionalArguments,
      final ResourceMethodDescriptor resourceMethod,
      final ResourceContext context,
      final DynamicRecordTemplate template) {
    List<Parameter<?>> parameters = resourceMethod.getParameters();
    Object[] arguments = Arrays.copyOf(positionalArguments, parameters.size());

    fixUpComplexKeySingletonArraysInArguments(arguments);

    for (int i = positionalArguments.length; i < parameters.size(); ++i) {
      Parameter<?> param = parameters.get(i);
      try {
        if (param.getParamType() == Parameter.ParamType.KEY
            || param.getParamType() == Parameter.ParamType.ASSOC_KEY_PARAM) {
          Object value = context.getPathKeys().get(param.getName());
          if (value != null) {
            arguments[i] = value;
            continue;
          }
        } else if (param.getParamType() == Parameter.ParamType.CALLBACK) {
          continue;
        } else if (param.getParamType() == Parameter.ParamType.PARSEQ_CONTEXT_PARAM
            || param.getParamType() == Parameter.ParamType.PARSEQ_CONTEXT) {
          continue; // don't know what to fill in yet
        } else if (param.getParamType() == Parameter.ParamType.HEADER) {
          HeaderParam headerParam = param.getAnnotations().get(HeaderParam.class);
          String value = context.getRequestHeaders().get(headerParam.value());
          arguments[i] = value;
          continue;
        }
        // Since we have multiple different types of MaskTrees that can be passed into resource
        // methods,
        // we must evaluate based on the param type (annotation used)
        else if (param.getParamType() == Parameter.ParamType.PROJECTION
            || param.getParamType() == Parameter.ParamType.PROJECTION_PARAM) {
          arguments[i] = context.getProjectionMask();
          continue;
        } else if (param.getParamType() == Parameter.ParamType.METADATA_PROJECTION_PARAM) {
          arguments[i] = context.getMetadataProjectionMask();
          continue;
        } else if (param.getParamType() == Parameter.ParamType.PAGING_PROJECTION_PARAM) {
          arguments[i] = context.getPagingProjectionMask();
          continue;
        } else if (param.getParamType() == Parameter.ParamType.CONTEXT
            || param.getParamType() == Parameter.ParamType.PAGING_CONTEXT_PARAM) {
          PagingContext ctx =
              RestUtils.getPagingContext(context, (PagingContext) param.getDefaultValue());
          arguments[i] = ctx;
          continue;
        } else if (param.getParamType() == Parameter.ParamType.PATH_KEYS
            || param.getParamType() == Parameter.ParamType.PATH_KEYS_PARAM) {
          arguments[i] = context.getPathKeys();
          continue;
        } else if (param.getParamType() == Parameter.ParamType.RESOURCE_CONTEXT
            || param.getParamType() == Parameter.ParamType.RESOURCE_CONTEXT_PARAM) {
          arguments[i] = context;
          continue;
        } else if (param.getParamType() == Parameter.ParamType.VALIDATOR_PARAM) {
          RestLiDataValidator validator =
              new RestLiDataValidator(
                  resourceMethod.getResourceModel().getResourceClass().getAnnotations(),
                  resourceMethod.getResourceModel().getValueClass(),
                  resourceMethod.getMethodType());
          arguments[i] = validator;
          continue;
        } else if (param.getParamType() == Parameter.ParamType.POST) {
          // handle action parameters
          if (template != null) {
            DataMap data = template.data();
            if (data.containsKey(param.getName())) {
              arguments[i] = template.getValue(param);
              continue;
            }
          }
        } else if (param.getParamType() == Parameter.ParamType.QUERY) {
          Object value;
          if (DataTemplate.class.isAssignableFrom(param.getType())) {
            value = buildDataTemplateArgument(context, param);
          } else {
            value = buildRegularArgument(context, param);
          }

          if (value != null) {
            arguments[i] = value;
            continue;
          }
        } else if (param.getParamType() == Parameter.ParamType.BATCH
            || param.getParamType() == Parameter.ParamType.RESOURCE_KEY) {
          // should not come to this routine since it should be handled by passing in
          // positionalArguments
          throw new RoutingException(
              "Parameter '" + param.getName() + "' should be passed in as a positional argument",
              HttpStatus.S_400_BAD_REQUEST.getCode());
        } else {
          // unknown param type
          throw new RoutingException(
              "Parameter '"
                  + param.getName()
                  + "' has an unknown parameter type '"
                  + param.getParamType().name()
                  + "'",
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
      } catch (TemplateRuntimeException e) {
        throw new RoutingException(
            "Parameter '" + param.getName() + "' is invalid",
            HttpStatus.S_400_BAD_REQUEST.getCode());
      }

      try {
        // Handling null-valued parameters not provided in resource context or entity body
        // check if it is optional parameter
        if (param.isOptional() && param.hasDefaultValue()) {
          arguments[i] = param.getDefaultValue();
        } else if (param.isOptional() && !param.getType().isPrimitive()) {
          // optional primitive parameter must have default value or provided
          arguments[i] = null;
        } else {
          throw new RoutingException(
              "Parameter '" + param.getName() + "' is required",
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
      } catch (ResourceConfigException e) {
        // Parameter default value format exception should result in server error code 500.
        throw new RestLiServiceException(
            HttpStatus.S_500_INTERNAL_SERVER_ERROR,
            "Parameter '" + param.getName() + "' default value is invalid",
            e);
      }
    }
    return arguments;
  }