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