예제 #1
0
  /**
   * @param request {@link RestRequest}
   * @param routingResult {@link RoutingResult}
   * @param responseObject response value
   * @return {@link RestResponse}
   * @throws IOException if cannot build response
   */
  public RestResponse buildResponse(
      final RestRequest request, final RoutingResult routingResult, final Object responseObject)
      throws IOException {
    Map<String, String> headers = new HashMap<String, String>();
    headers.putAll(((ServerResourceContext) routingResult.getContext()).getResponseHeaders());

    headers.put(
        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
        ((ServerResourceContext) routingResult.getContext()).getRestliProtocolVersion().toString());

    if (responseObject == null) {
      boolean isAction = routingResult.getResourceMethod().getType().equals(ResourceMethod.ACTION);
      RestResponseBuilder builder = new RestResponseBuilder();
      builder.setStatus(
          isAction ? HttpStatus.S_200_OK.getCode() : HttpStatus.S_404_NOT_FOUND.getCode());
      builder.setHeaders(headers);
      if (!isAction) {
        builder.setHeader(
            RestConstants.HEADER_LINKEDIN_ERROR_RESPONSE,
            RestConstants.HEADER_VALUE_ERROR_APPLICATION);
      }
      return builder.build();
    }

    RestLiResponseBuilder responseBuilder = chooseResponseBuilder(responseObject, routingResult);

    if (responseBuilder == null) {
      // this should not happen if valid return types are specified
      ResourceMethodDescriptor resourceMethod = routingResult.getResourceMethod();
      String fqMethodName =
          resourceMethod.getResourceModel().getResourceClass().getName()
              + '#'
              + routingResult.getResourceMethod().getMethod().getName();
      throw new RestLiInternalException(
          "Invalid return type '"
              + responseObject.getClass()
              + " from method '"
              + fqMethodName
              + '\'');
    }

    PartialRestResponse partialResponse =
        responseBuilder.buildResponse(request, routingResult, responseObject, headers);

    RestResponseBuilder builder =
        new RestResponseBuilder()
            .setHeaders(headers)
            .setStatus(partialResponse.getStatus().getCode());

    if (partialResponse.hasData()) {
      DataMap dataMap = partialResponse.getDataMap();
      String acceptTypes = request.getHeader(RestConstants.HEADER_ACCEPT);
      builder = encodeResult(builder, dataMap, acceptTypes);
    }

    return builder.build();
  }
예제 #2
0
 @Override
 public void onSuccess(RestLiRequestData requestData) {
   try {
     ResourceMethodDescriptor resourceMethodDescriptor = _invocableMethod.getResourceMethod();
     Object resource =
         _resourceFactory.create(resourceMethodDescriptor.getResourceModel().getResourceClass());
     if (BaseResource.class.isAssignableFrom(resource.getClass())) {
       ((BaseResource) resource).setContext(_invocableMethod.getContext());
     }
     Object[] args = _restLiArgumentBuilder.buildArguments(requestData, _invocableMethod);
     // Now invoke the resource implementation.
     doInvoke(
         resourceMethodDescriptor, _callback, _requestExecutionReportBuilder, resource, args);
   } catch (Exception e) {
     _callback.onError(
         e,
         _requestExecutionReportBuilder == null ? null : _requestExecutionReportBuilder.build());
   }
 }
예제 #3
0
 private boolean checkEngine(
     final RequestExecutionCallback<Object> callback,
     final ResourceMethodDescriptor desc,
     final RequestExecutionReportBuilder executionReportBuilder) {
   if (_engine == null) {
     final String fmt =
         "ParSeq based method %s.%s, but no engine given. "
             + "Check your RestLiServer construction, spring wiring, "
             + "and container-pegasus-restli-server-cmpt version.";
     final String clazz = desc.getResourceModel().getResourceClass().getName();
     final String method = desc.getMethod().getName();
     final String msg = String.format(fmt, clazz, method);
     callback.onError(
         new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR, msg),
         getRequestExecutionReport(executionReportBuilder));
     return false;
   } else {
     return true;
   }
 }
예제 #4
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);
  }
예제 #5
0
  public static CollectionMetadata buildMetadata(
      final URI requestUri,
      final ResourceContext resourceContext,
      final ResourceMethodDescriptor methodDescriptor,
      final List<?> resultElements,
      final PageIncrement pageIncrement,
      final Integer totalResults) {
    CollectionMetadata metadata = new CollectionMetadata();

    List<Parameter<?>> pagingContextParams =
        methodDescriptor.getParametersWithType(Parameter.ParamType.PAGING_CONTEXT_PARAM);
    PagingContext defaultPagingContext =
        pagingContextParams.isEmpty()
            ? null
            : (PagingContext) pagingContextParams.get(0).getDefaultValue();
    PagingContext pagingContext = getPagingContext(resourceContext, defaultPagingContext);

    metadata.setCount(pagingContext.getCount());
    metadata.setStart(pagingContext.getStart());

    if (totalResults != null) {
      metadata.setTotal(totalResults);
    } else {
      metadata.removeTotal();
    }

    LinkArray links = new LinkArray();

    String bestEncoding = RestConstants.HEADER_VALUE_APPLICATION_JSON;
    if (resourceContext.getRawRequest() != null) {
      bestEncoding =
          pickBestEncoding(resourceContext.getRequestHeaders().get(RestConstants.HEADER_ACCEPT));
    }

    // links use count as the step interval, so links don't make sense with count==0
    if (pagingContext.getCount() > 0) {
      // prev link
      if (pagingContext.getStart() > 0) {
        int prevStart = Math.max(0, pagingContext.getStart() - pagingContext.getCount());
        String prevUri = buildPaginatedUri(requestUri, prevStart, pagingContext.getCount());
        Link prevLink = new Link();
        prevLink.setRel("prev");
        prevLink.setHref(prevUri);
        prevLink.setType(bestEncoding);
        links.add(prevLink);
      }

      // next link if there are more results, or we returned a full page
      Integer nextStart =
          getNextPageStart(resultElements.size(), totalResults, pagingContext, pageIncrement);
      if (nextStart != null) {
        // R2 doesn't expose host/port => can't build absolute URI (this is ok, as
        // relative URIs internally
        String nextUri = buildPaginatedUri(requestUri, nextStart, pagingContext.getCount());
        Link nextLink = new Link();
        nextLink.setRel("next");
        nextLink.setHref(nextUri);
        nextLink.setType(bestEncoding);
        links.add(nextLink);
      }

      metadata.setLinks(links);
    }
    return metadata;
  }
예제 #6
0
  @SuppressWarnings("deprecation")
  private void doInvoke(
      final ResourceMethodDescriptor descriptor,
      final RequestExecutionCallback<Object> callback,
      final RequestExecutionReportBuilder requestExecutionReportBuilder,
      final Object resource,
      final Object... arguments)
      throws IllegalAccessException {
    Method method = descriptor.getMethod();

    try {
      switch (descriptor.getInterfaceType()) {
        case CALLBACK:
          int callbackIndex = descriptor.indexOfParameterType(ParamType.CALLBACK);
          final RequestExecutionReport executionReport =
              getRequestExecutionReport(requestExecutionReportBuilder);

          // Delegate the callback call to the request execution callback along with the
          // request execution report.
          arguments[callbackIndex] =
              new Callback<Object>() {
                @Override
                public void onError(Throwable e) {
                  callback.onError(
                      e instanceof RestLiServiceException
                          ? e
                          : new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR, e),
                      executionReport);
                }

                @Override
                public void onSuccess(Object result) {
                  callback.onSuccess(result, executionReport);
                }
              };

          method.invoke(resource, arguments);
          // App code should use the callback
          break;

        case SYNC:
          Object applicationResult = method.invoke(resource, arguments);
          callback.onSuccess(
              applicationResult, getRequestExecutionReport(requestExecutionReportBuilder));
          break;

        case PROMISE:
          if (!checkEngine(callback, descriptor, requestExecutionReportBuilder)) {
            break;
          }
          int contextIndex = descriptor.indexOfParameterType(ParamType.PARSEQ_CONTEXT_PARAM);

          if (contextIndex == -1) {
            contextIndex = descriptor.indexOfParameterType(ParamType.PARSEQ_CONTEXT);
          }
          // run through the engine to get the context
          Task<Object> restliTask = new RestLiParSeqTask(arguments, contextIndex, method, resource);

          // propagate the result to the callback
          restliTask.addListener(
              new CallbackPromiseAdapter<Object>(
                  callback, restliTask, requestExecutionReportBuilder));
          _engine.run(restliTask);
          break;

        case TASK:
          if (!checkEngine(callback, descriptor, requestExecutionReportBuilder)) {
            break;
          }

          // addListener requires Task<Object> in this case
          @SuppressWarnings("unchecked")
          Task<Object> task = (Task<Object>) method.invoke(resource, arguments);
          if (task == null) {
            callback.onError(
                new RestLiServiceException(
                    HttpStatus.S_500_INTERNAL_SERVER_ERROR, "Error in application code: null Task"),
                getRequestExecutionReport(requestExecutionReportBuilder));
          } else {
            task.addListener(
                new CallbackPromiseAdapter<Object>(callback, task, requestExecutionReportBuilder));
            _engine.run(task);
          }
          break;

        default:
          throw new AssertionError("Unexpected interface type " + descriptor.getInterfaceType());
      }
    } catch (InvocationTargetException e) {
      // Method runtime exceptions ar expected to fail with a top level
      // InvocationTargetException wrapped around the root cause.
      if (RestLiServiceException.class.isAssignableFrom(e.getCause().getClass())) {
        RestLiServiceException restLiServiceException = (RestLiServiceException) e.getCause();
        callback.onError(
            restLiServiceException, getRequestExecutionReport(requestExecutionReportBuilder));
      } else {
        callback.onError(
            new RestLiServiceException(
                HttpStatus.S_500_INTERNAL_SERVER_ERROR,
                _errorResponseBuilder.getInternalErrorMessage(),
                e.getCause()),
            getRequestExecutionReport(requestExecutionReportBuilder));
      }
    }
  }
예제 #7
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;
  }