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()));
}
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;
}
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);
}
}
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;
}
}
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();
}
Aggregations