/** * Return a list of parameters with the provided type. * * @param type {@link Parameter.ParamType} to filter by * @return list of parameters that match the provided type */ public List<Parameter<?>> getParametersWithType(final Parameter.ParamType type) { List<Parameter<?>> params = new ArrayList<Parameter<?>>(); for (Parameter<?> p : _parameters) { if (p.getParamType() == type) { params.add(p); } } return params; }
/** * Get parameter value by name. * * @param name parameter name * @param <T> parameter value type * @return parameter value */ @SuppressWarnings("unchecked") public <T> Parameter<T> getParameter(final String name) { for (Parameter<?> p : _parameters) { if (p.getName().equals(name)) { return (Parameter<T>) p; } } return null; }
private static DataTemplate<?> buildDataTemplateArgument( final ResourceContext context, final Parameter<?> param) { Object paramValue = context.getStructuredParameter(param.getName()); DataTemplate<?> paramRecordTemplate; if (paramValue == null) { return null; } else { @SuppressWarnings("unchecked") final Class<? extends RecordTemplate> paramType = (Class<? extends RecordTemplate>) param.getType(); /** * It is possible for the paramValue provided by ResourceContext to be coerced to the wrong * type. If a query param is a single value param for example www.domain.com/resource?foo=1. * Then ResourceContext will parse foo as a String with value = 1. However if a query param * contains many values for example www.domain.com/resource?foo=1&foo=2&foo=3 Then * ResourceContext will parse foo as an DataList with value [1,2,3] * * <p>So this means if the 'final' type of a query param is an Array and the paramValue we * received from ResourceContext is not a DataList we will have to wrap the paramValue inside * a DataList */ if (AbstractArrayTemplate.class.isAssignableFrom(paramType) && paramValue.getClass() != DataList.class) { paramRecordTemplate = DataTemplateUtil.wrap(new DataList(Arrays.asList(paramValue)), paramType); } else { paramRecordTemplate = DataTemplateUtil.wrap(paramValue, paramType); } // Validate against the class schema with FixupMode.STRING_TO_PRIMITIVE to parse the // strings into the corresponding primitive types. ValidateDataAgainstSchema.validate( paramRecordTemplate.data(), paramRecordTemplate.schema(), new ValidationOptions( RequiredMode.CAN_BE_ABSENT_IF_HAS_DEFAULT, CoercionMode.STRING_TO_PRIMITIVE)); return paramRecordTemplate; } }
/** * 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; }
/** * 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; }
/** * 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; }