Search in sources :

Example 21 with RecordTemplate

use of com.linkedin.data.template.RecordTemplate in project rest.li by linkedin.

the class RestLiDataValidator method validatePatch.

/**
   * Checks that if the patch is applied to a valid entity, the modified entity will also be valid.
   * This method
   * (1) Checks that required/ReadOnly/CreateOnly fields are not deleted.
   * (2) Checks that new values for record templates contain all required fields.
   * (3) Applies the patch to an empty entity and validates the entity for custom validation rules
   * and Rest.li annotations (Allows required fields to be absent by using {@link RequiredMode#IGNORE},
   * because a patch does not necessarily contain all fields).
   *
   * NOTE: Updating a part of an array is not supported. So if the array contains a required field that is
   * readonly or createonly, the field cannot be present (no partial updates on readonly/createonly)
   * but cannot be absent either (no missing required fields). This means the array cannot be changed by a
   * partial update request. This is something that should be fixed.
   *
   * @param patchRequest the patch
   * @return the final validation result
   */
private ValidationResult validatePatch(PatchRequest<?> patchRequest) {
    // Instantiate an empty entity.
    RecordTemplate entity;
    try {
        entity = _valueClass.newInstance();
    } catch (InstantiationException e) {
        return validationResultWithErrorMessage(INSTANTIATION_ERROR);
    } catch (IllegalAccessException e) {
        return validationResultWithErrorMessage(ILLEGAL_ACCESS_ERROR);
    }
    // Apply the patch to the entity and get paths that $set and $delete operations were performed on.
    @SuppressWarnings("unchecked") PatchRequest<RecordTemplate> patch = (PatchRequest<RecordTemplate>) patchRequest;
    DataComplexProcessor processor = new DataComplexProcessor(new Patch(true), patch.getPatchDocument(), entity.data());
    MessageList<Message> messages;
    try {
        messages = processor.runDataProcessing(false);
    } catch (DataProcessingException e) {
        return validationResultWithErrorMessage("Error while applying patch: " + e.getMessage());
    }
    ValidationErrorResult checkDeleteResult = new ValidationErrorResult();
    checkDeletesAreValid(entity.schema(), messages, checkDeleteResult);
    if (!checkDeleteResult.isValid()) {
        return checkDeleteResult;
    }
    ValidationResult checkSetResult = checkNewRecordsAreNotMissingFields(entity, messages);
    if (checkSetResult != null) {
        return checkSetResult;
    }
    // It's okay if required fields are absent in a partial update request, so use ignore mode.
    return ValidateDataAgainstSchema.validate(new SimpleDataElement(entity.data(), entity.schema()), new ValidationOptions(RequiredMode.IGNORE), new DataValidator(entity.schema()));
}
Also used : Message(com.linkedin.data.message.Message) PatchRequest(com.linkedin.restli.common.PatchRequest) DataComplexProcessor(com.linkedin.data.transform.DataComplexProcessor) ValidationResult(com.linkedin.data.schema.validation.ValidationResult) ValidationOptions(com.linkedin.data.schema.validation.ValidationOptions) DataProcessingException(com.linkedin.data.transform.DataProcessingException) RecordTemplate(com.linkedin.data.template.RecordTemplate) SimpleDataElement(com.linkedin.data.element.SimpleDataElement) Patch(com.linkedin.data.transform.patch.Patch)

Example 22 with RecordTemplate

use of com.linkedin.data.template.RecordTemplate in project rest.li by linkedin.

the class RestLiAnnotationReader method processCollection.

@SuppressWarnings("unchecked")
private static ResourceModel processCollection(final Class<? extends KeyValueResource<?, ?>> collectionResourceClass, ResourceModel parentResourceModel) {
    Class<?> keyClass;
    Class<? extends RecordTemplate> keyKeyClass = null;
    Class<? extends RecordTemplate> keyParamsClass = null;
    Class<? extends RecordTemplate> valueClass;
    Class<?> complexKeyResourceBase = null;
    // type V and the resource key type is ComplexResourceKey<K,P>
    if (ComplexKeyResource.class.isAssignableFrom(collectionResourceClass)) {
        complexKeyResourceBase = ComplexKeyResource.class;
    } else if (ComplexKeyResourceAsync.class.isAssignableFrom(collectionResourceClass)) {
        complexKeyResourceBase = ComplexKeyResourceAsync.class;
    } else if (ComplexKeyResourceTask.class.isAssignableFrom(collectionResourceClass)) {
        complexKeyResourceBase = ComplexKeyResourceTask.class;
    } else if (ComplexKeyResourcePromise.class.isAssignableFrom(collectionResourceClass)) {
        complexKeyResourceBase = ComplexKeyResourcePromise.class;
    }
    if (complexKeyResourceBase != null) {
        List<Class<?>> kvParams;
        if (complexKeyResourceBase.equals(ComplexKeyResource.class)) {
            kvParams = ReflectionUtils.getTypeArguments(ComplexKeyResource.class, (Class<? extends ComplexKeyResource<?, ?, ?>>) collectionResourceClass);
        } else if (complexKeyResourceBase.equals(ComplexKeyResourceAsync.class)) {
            kvParams = ReflectionUtils.getTypeArguments(ComplexKeyResourceAsync.class, (Class<? extends ComplexKeyResourceAsync<?, ?, ?>>) collectionResourceClass);
        } else if (complexKeyResourceBase.equals(ComplexKeyResourceTask.class)) {
            kvParams = ReflectionUtils.getTypeArguments(ComplexKeyResourceTask.class, (Class<? extends ComplexKeyResourceTask<?, ?, ?>>) collectionResourceClass);
        } else {
            kvParams = ReflectionUtils.getTypeArguments(ComplexKeyResourcePromise.class, (Class<? extends ComplexKeyResourcePromise<?, ?, ?>>) collectionResourceClass);
        }
        keyClass = ComplexResourceKey.class;
        keyKeyClass = kvParams.get(0).asSubclass(RecordTemplate.class);
        keyParamsClass = kvParams.get(1).asSubclass(RecordTemplate.class);
        valueClass = kvParams.get(2).asSubclass(RecordTemplate.class);
    } else // Otherwise, it's a KeyValueResource, whose parameters are resource key and resource
    // value
    {
        List<Type> actualTypeArguments = ReflectionUtils.getTypeArgumentsParametrized(KeyValueResource.class, collectionResourceClass);
        keyClass = ReflectionUtils.getClass(actualTypeArguments.get(0));
        if (RecordTemplate.class.isAssignableFrom(keyClass)) {
            // ComplexResourceKey
            throw new ResourceConfigException("Class '" + collectionResourceClass.getName() + "' should implement 'ComplexKeyResource' as a complex key '" + keyClass.getName() + "' is being used.");
        } else if (TyperefInfo.class.isAssignableFrom(keyClass)) {
            throw new ResourceConfigException("Typeref '" + keyClass.getName() + "' cannot be key type for class '" + collectionResourceClass.getName() + "'.");
        }
        if (keyClass.equals(ComplexResourceKey.class)) {
            @SuppressWarnings("unchecked") Type[] typeArguments = ((ParameterizedType) actualTypeArguments.get(0)).getActualTypeArguments();
            keyKeyClass = ReflectionUtils.getClass(typeArguments[0]).asSubclass(RecordTemplate.class);
            keyParamsClass = ReflectionUtils.getClass(typeArguments[1]).asSubclass(RecordTemplate.class);
        }
        valueClass = ReflectionUtils.getClass(actualTypeArguments.get(1)).asSubclass(RecordTemplate.class);
    }
    ResourceType resourceType = getResourceType(collectionResourceClass);
    RestLiAnnotationData annotationData;
    if (collectionResourceClass.isAnnotationPresent(RestLiCollection.class)) {
        annotationData = new RestLiAnnotationData(collectionResourceClass.getAnnotation(RestLiCollection.class));
    } else if (collectionResourceClass.isAnnotationPresent(RestLiAssociation.class)) {
        annotationData = new RestLiAnnotationData(collectionResourceClass.getAnnotation(RestLiAssociation.class));
    } else {
        throw new ResourceConfigException("No valid annotation on resource class '" + collectionResourceClass.getName() + "'");
    }
    String name = annotationData.name();
    String namespace = annotationData.namespace();
    String keyName;
    if (annotationData.keyName() == null) {
        keyName = name + "Id";
    } else {
        keyName = annotationData.keyName();
    }
    Key primaryKey = buildKey(name, keyName, keyClass, annotationData.typerefInfoClass());
    Set<Key> keys = new HashSet<Key>();
    if (annotationData.keys() == null) {
        keys.add(primaryKey);
    } else {
        keys.addAll(buildKeys(name, annotationData.keys()));
    }
    Class<?> parentResourceClass = annotationData.parent().equals(RestAnnotations.ROOT.class) ? null : annotationData.parent();
    ResourceModel collectionModel = new ResourceModel(primaryKey, keyKeyClass, keyParamsClass, keys, valueClass, collectionResourceClass, parentResourceClass, name, resourceType, namespace);
    collectionModel.setParentResourceModel(parentResourceModel);
    addResourceMethods(collectionResourceClass, collectionModel);
    log.info("Processed collection resource '" + collectionResourceClass.getName() + "'");
    return collectionModel;
}
Also used : RestLiAssociation(com.linkedin.restli.server.annotations.RestLiAssociation) ComplexKeyResourceTask(com.linkedin.restli.server.resources.ComplexKeyResourceTask) ComplexKeyResource(com.linkedin.restli.server.resources.ComplexKeyResource) ParameterizedType(java.lang.reflect.ParameterizedType) RecordTemplate(com.linkedin.data.template.RecordTemplate) ComplexKeyResourcePromise(com.linkedin.restli.server.resources.ComplexKeyResourcePromise) HashSet(java.util.HashSet) ComplexKeyResourceAsync(com.linkedin.restli.server.resources.ComplexKeyResourceAsync) TyperefInfo(com.linkedin.data.template.TyperefInfo) HasTyperefInfo(com.linkedin.data.template.HasTyperefInfo) ParameterizedType(java.lang.reflect.ParameterizedType) InterfaceType(com.linkedin.restli.internal.server.model.ResourceMethodDescriptor.InterfaceType) Type(java.lang.reflect.Type) ResourceConfigException(com.linkedin.restli.server.ResourceConfigException) AlternativeKey(com.linkedin.restli.server.annotations.AlternativeKey) ComplexResourceKey(com.linkedin.restli.common.ComplexResourceKey) Key(com.linkedin.restli.server.Key) AssocKey(com.linkedin.restli.server.annotations.AssocKey)

Example 23 with RecordTemplate

use of com.linkedin.data.template.RecordTemplate in project rest.li by linkedin.

the class RestLiAnnotationReader method addFinderResourceMethod.

private static void addFinderResourceMethod(final ResourceModel model, final Method method) {
    Finder finderAnno = method.getAnnotation(Finder.class);
    if (finderAnno == null) {
        return;
    }
    String queryType = finderAnno.value();
    List<Parameter<?>> queryParameters = getParameters(model, method, ResourceMethod.FINDER);
    if (queryType != null) {
        Class<? extends RecordTemplate> metadataType = null;
        final Class<?> returnClass = getLogicalReturnClass(method);
        if (CollectionResult.class.isAssignableFrom(returnClass)) {
            final List<Class<?>> typeArguments = ReflectionUtils.getTypeArguments(CollectionResult.class, returnClass.asSubclass(CollectionResult.class));
            final Class<?> metadataClass;
            if (typeArguments == null || typeArguments.get(1) == null) {
                // the return type may leave metadata type as parameterized and specify in runtime
                metadataClass = ((Class<?>) ((ParameterizedType) getLogicalReturnType(method)).getActualTypeArguments()[1]);
            } else {
                metadataClass = typeArguments.get(1);
            }
            if (!metadataClass.equals(NoMetadata.class)) {
                metadataType = metadataClass.asSubclass(RecordTemplate.class);
            }
        }
        DataMap annotationsMap = ResourceModelAnnotation.getAnnotationsMap(method.getAnnotations());
        addDeprecatedAnnotation(annotationsMap, method);
        ResourceMethodDescriptor finderMethodDescriptor = ResourceMethodDescriptor.createForFinder(method, queryParameters, queryType, metadataType, getInterfaceType(method), annotationsMap);
        validateFinderMethod(finderMethodDescriptor, model);
        if (!Modifier.isPublic(method.getModifiers())) {
            throw new ResourceConfigException(String.format("Resource '%s' contains non-public finder method '%s'.", model.getName(), method.getName()));
        }
        model.addResourceMethodDescriptor(finderMethodDescriptor);
    }
}
Also used : NoMetadata(com.linkedin.restli.server.NoMetadata) Finder(com.linkedin.restli.server.annotations.Finder) DataMap(com.linkedin.data.DataMap) ParameterizedType(java.lang.reflect.ParameterizedType) CollectionResult(com.linkedin.restli.server.CollectionResult) RecordTemplate(com.linkedin.data.template.RecordTemplate) ResourceConfigException(com.linkedin.restli.server.ResourceConfigException)

Example 24 with RecordTemplate

use of com.linkedin.data.template.RecordTemplate in project rest.li by linkedin.

the class ArgumentBuilder method buildDataTemplateArgument.

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]
       *
       * 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;
    }
}
Also used : DataList(com.linkedin.data.DataList) RecordTemplate(com.linkedin.data.template.RecordTemplate) DynamicRecordTemplate(com.linkedin.data.template.DynamicRecordTemplate) AbstractArrayTemplate(com.linkedin.data.template.AbstractArrayTemplate) ValidationOptions(com.linkedin.data.schema.validation.ValidationOptions)

Example 25 with RecordTemplate

use of com.linkedin.data.template.RecordTemplate in project rest.li by linkedin.

the class BatchCreateArgumentBuilder method extractRequestData.

@Override
public RestLiRequestData extractRequestData(RoutingResult routingResult, RestRequest request) {
    Class<? extends RecordTemplate> valueClass = ArgumentUtils.getValueClass(routingResult);
    DataMap dataMap = DataMapUtils.readMap(request);
    @SuppressWarnings({ "unchecked", "rawtypes" }) CollectionRequest<RecordTemplate> collectionRequest = new CollectionRequest(dataMap, valueClass);
    return new RestLiRequestDataImpl.Builder().batchEntities(collectionRequest.getElements()).build();
}
Also used : CollectionRequest(com.linkedin.restli.common.CollectionRequest) RecordTemplate(com.linkedin.data.template.RecordTemplate) DataMap(com.linkedin.data.DataMap)

Aggregations

RecordTemplate (com.linkedin.data.template.RecordTemplate)59 DataMap (com.linkedin.data.DataMap)23 Test (org.testng.annotations.Test)23 RestLiServiceException (com.linkedin.restli.server.RestLiServiceException)10 ComplexResourceKey (com.linkedin.restli.common.ComplexResourceKey)9 DataList (com.linkedin.data.DataList)8 DynamicRecordTemplate (com.linkedin.data.template.DynamicRecordTemplate)8 ArrayList (java.util.ArrayList)8 PathSpec (com.linkedin.data.schema.PathSpec)7 TestRecord (com.linkedin.restli.client.test.TestRecord)7 AnyRecord (com.linkedin.restli.internal.server.methods.AnyRecord)7 FilterResponseContext (com.linkedin.restli.server.filter.FilterResponseContext)7 BeforeTest (org.testng.annotations.BeforeTest)7 ByteString (com.linkedin.data.ByteString)6 RestResponseBuilder (com.linkedin.r2.message.rest.RestResponseBuilder)6 EmptyRecord (com.linkedin.restli.common.EmptyRecord)6 RequestExecutionReport (com.linkedin.restli.server.RequestExecutionReport)6 RequestExecutionReportBuilder (com.linkedin.restli.server.RequestExecutionReportBuilder)6 RestLiResponseAttachments (com.linkedin.restli.server.RestLiResponseAttachments)6 RoutingException (com.linkedin.restli.server.RoutingException)6