Esempio n. 1
0
 /**
  * Similar to {@link #readMap(com.linkedin.r2.message.rest.RestMessage)}, but will throw an {@link
  * IOException} instead of a {@link RestLiInternalException}
  *
  * @throws IOException if the message entity cannot be parsed.
  */
 private static DataMap readMapWithExceptions(final RestMessage message) throws IOException {
   try {
     return DataMapConverter.bytesToDataMap(
         message.getHeader(RestConstants.HEADER_CONTENT_TYPE), message.getEntity());
   } catch (MimeTypeParseException e) {
     throw new RoutingException(e.getMessage(), HttpStatus.S_400_BAD_REQUEST.getCode(), e);
   }
 }
Esempio n. 2
0
 /**
  * @param request {@link com.linkedin.r2.message.rest.RestRequest}
  * @param recordClass resource value class
  * @param <V> resource value type which is a subclass of {@link RecordTemplate}
  * @return resource value
  */
 public static <V extends RecordTemplate> V extractEntity(
     final RestRequest request, final Class<V> recordClass) {
   try {
     return DataMapUtils.read(request, recordClass);
   } catch (IOException e) {
     throw new RoutingException(
         "Error parsing entity body: " + e.getMessage(), HttpStatus.S_400_BAD_REQUEST.getCode());
   }
 }
Esempio n. 3
0
  /**
   * Build a method argument from a request parameter that is NOT backed by a schema, i.e. a
   * primitive or an array
   *
   * @param context {@link ResourceContext}
   * @param param {@link Parameter}
   * @return argument value in the correct type
   */
  private static Object buildRegularArgument(
      final ResourceContext context, final Parameter<?> param) {
    String value =
        ArgumentUtils.argumentAsString(context.getParameter(param.getName()), param.getName());

    final Object convertedValue;
    if (value == null) {
      return null;
    } else {
      if (param.isArray()) {
        convertedValue = buildArrayArgument(context, param);
      } else {
        try {
          convertedValue =
              ArgumentUtils.convertSimpleValue(value, param.getDataSchema(), param.getType());
        } catch (NumberFormatException e) {
          Class<?> targetClass =
              DataSchemaUtil.dataSchemaTypeToPrimitiveDataSchemaClass(
                  param.getDataSchema().getDereferencedType());
          // thrown from Integer.valueOf or Long.valueOf
          throw new RoutingException(
              String.format(
                  "Argument parameter '%s' value '%s' must be of type '%s'",
                  param.getName(), value, targetClass.getName()),
              HttpStatus.S_400_BAD_REQUEST.getCode());
        } catch (IllegalArgumentException e) {
          // thrown from Enum.valueOf
          throw new RoutingException(
              String.format(
                  "Argument parameter '%s' value '%s' is invalid", param.getName(), value),
              HttpStatus.S_400_BAD_REQUEST.getCode());
        } catch (TemplateRuntimeException e) {
          // thrown from DataTemplateUtil.coerceOutput
          throw new RoutingException(
              String.format(
                  "Argument parameter '%s' value '%s' is invalid. Reason: %s",
                  param.getName(), value, e.getMessage()),
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
      }
    }

    return convertedValue;
  }
Esempio n. 4
0
  /**
   * Convert a DataMap representation of a BatchRequest (string->record) into a Java Map appropriate
   * for passing into application code. Note that compound/complex keys are represented as their
   * string encoding in the DataMap. Since we have already parsed these keys, we simply try to match
   * the string representations, rather than re-parsing.
   *
   * @param data - the input DataMap to be converted
   * @param valueClass - the RecordTemplate type of the values
   * @param ids - the parsed batch ids from the request URI
   * @return a map using appropriate key and value classes, or null if ids is null
   */
  public static <R extends RecordTemplate> Map<Object, R> buildBatchRequestMap(
      final DataMap data,
      final Class<R> valueClass,
      final Set<?> ids,
      final ProtocolVersion version) {
    if (ids == null) {
      return null;
    }

    BatchRequest<R> batchRequest = new BatchRequest<R>(data, new TypeSpec<R>(valueClass));

    Map<String, Object> parsedKeyMap = new HashMap<String, Object>();
    for (Object o : ids) {
      parsedKeyMap.put(URIParamUtils.encodeKeyForBody(o, true, version), o);
    }

    Map<Object, R> result =
        new HashMap<Object, R>(
            CollectionUtils.getMapInitialCapacity(batchRequest.getEntities().size(), 0.75f), 0.75f);
    for (Map.Entry<String, R> entry : batchRequest.getEntities().entrySet()) {
      Object key = parsedKeyMap.get(entry.getKey());
      if (key == null) {
        throw new RoutingException(
            String.format(
                "Batch request mismatch, URI keys: '%s'  Entity keys: '%s'",
                ids.toString(), batchRequest.getEntities().keySet().toString()),
            HttpStatus.S_400_BAD_REQUEST.getCode());
      }
      R value = DataTemplateUtil.wrap(entry.getValue().data(), valueClass);
      result.put(key, value);
    }
    if (!ids.equals(result.keySet())) {
      throw new RoutingException(
          String.format(
              "Batch request mismatch, URI keys: '%s'  Entity keys: '%s'",
              ids.toString(), result.keySet().toString()),
          HttpStatus.S_400_BAD_REQUEST.getCode());
    }
    return result;
  }
  @Override
  @SuppressWarnings("fallthrough")
  public RestResponse processDocumentationRequest(RestRequest request) {
    final String path = request.getURI().getRawPath();
    final List<UriComponent.PathSegment> pathSegments = UriComponent.decodePath(path, true);

    String prefixSegment = null;
    String actionSegment = null;
    String typeSegment = null;
    String objectSegment = null;

    switch (pathSegments.size()) {
      case 5:
        objectSegment = pathSegments.get(4).getPath();
      case 4:
        typeSegment = pathSegments.get(3).getPath();
      case 3:
        actionSegment = pathSegments.get(2).getPath();
      case 2:
        prefixSegment = pathSegments.get(1).getPath();
    }

    assert (prefixSegment.equals(DOC_PREFIX)
        || (HttpMethod.valueOf(request.getMethod()) == HttpMethod.OPTIONS));

    final ByteArrayOutputStream out = new ByteArrayOutputStream(BAOS_BUFFER_SIZE);
    final RestLiDocumentationRenderer renderer;

    if (HttpMethod.valueOf(request.getMethod()) == HttpMethod.OPTIONS) {
      renderer = _jsonRenderer;
      renderer.renderResource(prefixSegment, out);
    } else if (HttpMethod.valueOf(request.getMethod()) == HttpMethod.GET) {
      if (!DOC_VIEW_DOCS_ACTION.equals(actionSegment)) {
        throw createRoutingError(path);
      }

      final MultivaluedMap queryMap = UriComponent.decodeQuery(request.getURI().getQuery(), false);
      final List<String> formatList = queryMap.get("format");
      if (formatList == null) {
        renderer = _htmlRenderer;
      } else if (formatList.size() > 1) {
        throw new RoutingException(
            String.format(
                "\"format\" query parameter must be unique, where multiple are specified: %s",
                Arrays.toString(formatList.toArray())),
            HttpStatus.S_400_BAD_REQUEST.getCode());
      } else {
        renderer = (formatList.contains(DOC_JSON_FORMAT) ? _jsonRenderer : _htmlRenderer);
      }

      if (renderer == _htmlRenderer) {
        _htmlRenderer.setJsonFormatUri(
            UriBuilder.fromUri(request.getURI()).queryParam("format", DOC_JSON_FORMAT).build());
      }

      try {
        if (typeSegment == null || typeSegment.isEmpty()) {
          renderer.renderHome(out);
        } else {
          if (DOC_RESOURCE_TYPE.equals(typeSegment)) {
            if (objectSegment == null || objectSegment.isEmpty()) {
              renderer.renderResourceHome(out);
            } else {
              renderer.renderResource(objectSegment, out);
            }
          } else if (DOC_DATA_TYPE.equals(typeSegment)) {
            if (objectSegment == null || objectSegment.isEmpty()) {
              renderer.renderDataModelHome(out);
            } else {
              renderer.renderDataModel(objectSegment, out);
            }
          } else {
            throw createRoutingError(path);
          }
        }
      } catch (RuntimeException e) {
        if (!renderer.handleException(e, out)) {
          throw e;
        }
      }
    } else {
      throw new RoutingException(HttpStatus.S_405_METHOD_NOT_ALLOWED.getCode());
    }

    return new RestResponseBuilder()
        .setStatus(HttpStatus.S_200_OK.getCode())
        .setHeader(RestConstants.HEADER_CONTENT_TYPE, renderer.getMIMEType())
        .setEntity(out.toByteArray())
        .build();
  }
Esempio n. 6
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;
  }
Esempio n. 7
0
  /**
   * Build a method argument from a request parameter that is an array
   *
   * @param context {@link ResourceContext}
   * @param param {@link Parameter}
   * @return argument value in the correct type
   */
  private static Object buildArrayArgument(
      final ResourceContext context, final Parameter<?> param) {
    final Object convertedValue;
    if (DataTemplate.class.isAssignableFrom(param.getItemType())) {
      final DataList itemsList = (DataList) context.getStructuredParameter(param.getName());
      convertedValue = Array.newInstance(param.getItemType(), itemsList.size());
      int j = 0;
      for (Object paramData : itemsList) {
        final DataTemplate<?> itemsElem =
            DataTemplateUtil.wrap(paramData, param.getItemType().asSubclass(DataTemplate.class));

        ValidateDataAgainstSchema.validate(
            itemsElem.data(),
            itemsElem.schema(),
            new ValidationOptions(
                RequiredMode.CAN_BE_ABSENT_IF_HAS_DEFAULT, CoercionMode.STRING_TO_PRIMITIVE));

        Array.set(convertedValue, j++, itemsElem);
      }
    } else {
      final List<String> itemStringValues = context.getParameterValues(param.getName());
      ArrayDataSchema parameterSchema = null;
      if (param.getDataSchema() instanceof ArrayDataSchema) {
        parameterSchema = (ArrayDataSchema) param.getDataSchema();
      } else {
        throw new RoutingException(
            "An array schema is expected.", HttpStatus.S_400_BAD_REQUEST.getCode());
      }

      convertedValue = Array.newInstance(param.getItemType(), itemStringValues.size());
      int j = 0;
      for (String itemStringValue : itemStringValues) {
        if (itemStringValue == null) {
          throw new RoutingException(
              "Parameter '" + param.getName() + "' cannot contain null values",
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
        try {
          Array.set(
              convertedValue,
              j++,
              ArgumentUtils.convertSimpleValue(
                  itemStringValue, parameterSchema.getItems(), param.getItemType()));
        } catch (NumberFormatException e) {
          Class<?> targetClass =
              DataSchemaUtil.dataSchemaTypeToPrimitiveDataSchemaClass(
                  parameterSchema.getItems().getDereferencedType());
          // thrown from Integer.valueOf or Long.valueOf
          throw new RoutingException(
              String.format(
                  "Array parameter '%s' value '%s' must be of type '%s'",
                  param.getName(), itemStringValue, targetClass.getName()),
              HttpStatus.S_400_BAD_REQUEST.getCode());
        } catch (IllegalArgumentException e) {
          // thrown from Enum.valueOf
          throw new RoutingException(
              String.format(
                  "Array parameter '%s' value '%s' is invalid", param.getName(), itemStringValue),
              HttpStatus.S_400_BAD_REQUEST.getCode());
        } catch (TemplateRuntimeException e) {
          // thrown from DataTemplateUtil.coerceOutput
          throw new RoutingException(
              String.format(
                  "Array parameter '%s' value '%s' is invalid. Reason: %s",
                  param.getName(), itemStringValue, e.getMessage()),
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
      }
    }

    return convertedValue;
  }