/*
Copyright (c) 2015 LinkedIn Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.linkedin.restli.examples.greetings.server;
import com.linkedin.data.message.Message;
import com.linkedin.data.schema.validation.ValidationResult;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.common.PatchRequest;
import com.linkedin.restli.common.validation.CreateOnly;
import com.linkedin.restli.common.validation.ReadOnly;
import com.linkedin.restli.common.validation.RestLiDataValidator;
import com.linkedin.restli.examples.greetings.api.ValidationDemo;
import com.linkedin.restli.examples.greetings.api.myEnum;
import com.linkedin.restli.examples.greetings.api.myRecord;
import com.linkedin.restli.server.BatchCreateRequest;
import com.linkedin.restli.server.BatchCreateResult;
import com.linkedin.restli.server.BatchPatchRequest;
import com.linkedin.restli.server.BatchUpdateRequest;
import com.linkedin.restli.server.BatchUpdateResult;
import com.linkedin.restli.server.CreateResponse;
import com.linkedin.restli.server.RestLiServiceException;
import com.linkedin.restli.server.UpdateResponse;
import com.linkedin.restli.server.annotations.Finder;
import com.linkedin.restli.server.annotations.QueryParam;
import com.linkedin.restli.server.annotations.RestLiCollection;
import com.linkedin.restli.server.annotations.RestMethod;
import com.linkedin.restli.server.annotations.ValidatorParam;
import com.linkedin.restli.server.resources.KeyValueResource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Free-form resource for testing Rest.li data validation.
* This class shows how to validate data manually by injecting the validator as a resource method parameter.
* Outgoing data that fails validation is corrected before it is sent to the client.
*
* @author Soojung Ha
*/
@RestLiCollection(name = "validationDemos", namespace = "com.linkedin.restli.examples.greetings.client")
@ReadOnly({"stringA", "intA", "UnionFieldWithInlineRecord/com.linkedin.restli.examples.greetings.api.myRecord/foo1",
"ArrayWithInlineRecord/*/bar1", "validationDemoNext/stringB", "validationDemoNext/UnionFieldWithInlineRecord"})
@CreateOnly({"stringB", "intB", "UnionFieldWithInlineRecord/com.linkedin.restli.examples.greetings.api.myRecord/foo2",
"MapWithTyperefs/*/id"})
public class ValidationDemoResource implements KeyValueResource<Integer, ValidationDemo>
{
@RestMethod.Create
public CreateResponse create(final ValidationDemo entity, @ValidatorParam RestLiDataValidator validator)
{
ValidationResult result = validator.validateInput(entity);
if (!result.isValid())
{
throw new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString());
}
return new CreateResponse(1234);
}
@RestMethod.BatchCreate
public BatchCreateResult<Integer, ValidationDemo> batchCreate(final BatchCreateRequest<Integer, ValidationDemo> entities,
@ValidatorParam RestLiDataValidator validator)
{
List<CreateResponse> results = new ArrayList<CreateResponse>();
int id = 0;
for (ValidationDemo entity : entities.getInput())
{
ValidationResult result = validator.validateInput(entity);
if (result.isValid())
{
results.add(new CreateResponse(id));
id++;
}
else
{
results.add(new CreateResponse(new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString())));
}
}
return new BatchCreateResult<Integer, ValidationDemo>(results);
}
@RestMethod.Update
public UpdateResponse update(final Integer key, final ValidationDemo entity, @ValidatorParam RestLiDataValidator validator)
{
ValidationResult result = validator.validateInput(entity);
if (!result.isValid())
{
throw new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString());
}
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
@RestMethod.BatchUpdate
public BatchUpdateResult<Integer, ValidationDemo> batchUpdate(final BatchUpdateRequest<Integer, ValidationDemo> entities,
@ValidatorParam RestLiDataValidator validator)
{
Map<Integer, UpdateResponse> results = new HashMap<Integer, UpdateResponse>();
Map<Integer, RestLiServiceException> errors = new HashMap<Integer, RestLiServiceException>();
for (Map.Entry<Integer, ValidationDemo> entry : entities.getData().entrySet())
{
Integer key = entry.getKey();
ValidationDemo entity = entry.getValue();
ValidationResult result = validator.validateInput(entity);
if (result.isValid())
{
results.put(key, new UpdateResponse(HttpStatus.S_204_NO_CONTENT));
}
else
{
errors.put(key, new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString()));
}
}
return new BatchUpdateResult<Integer, ValidationDemo>(results, errors);
}
@RestMethod.PartialUpdate
public UpdateResponse update(final Integer key, final PatchRequest<ValidationDemo> patch,
@ValidatorParam RestLiDataValidator validator)
{
ValidationResult result = validator.validateInput(patch);
if (!result.isValid())
{
for (Message message : result.getMessages())
{
System.out.println(message);
}
throw new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString());
}
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
@RestMethod.BatchPartialUpdate
public BatchUpdateResult<Integer, ValidationDemo> batchUpdate(final BatchPatchRequest<Integer, ValidationDemo> entityUpdates,
@ValidatorParam RestLiDataValidator validator)
{
Map<Integer, UpdateResponse> results = new HashMap<Integer, UpdateResponse>();
Map<Integer, RestLiServiceException> errors = new HashMap<Integer, RestLiServiceException>();
for (Map.Entry<Integer, PatchRequest<ValidationDemo>> entry : entityUpdates.getData().entrySet())
{
Integer key = entry.getKey();
PatchRequest<ValidationDemo> patch = entry.getValue();
ValidationResult result = validator.validateInput(patch);
if (result.isValid())
{
results.put(key, new UpdateResponse(HttpStatus.S_204_NO_CONTENT));
}
else
{
errors.put(key, new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString()));
}
}
return new BatchUpdateResult<Integer, ValidationDemo>(results, errors);
}
private void check(boolean condition)
{
if (!condition)
{
throw new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR);
}
}
@RestMethod.Get
public ValidationDemo get(final Integer key, @ValidatorParam RestLiDataValidator validator)
{
// Generate an entity that does not conform to the data schema
ValidationDemo.UnionFieldWithInlineRecord union = new ValidationDemo.UnionFieldWithInlineRecord();
union.setMyEnum(myEnum.BARBAR);
ValidationDemo validationDemo = new ValidationDemo().setStringA("stringA is readOnly").setUnionFieldWithInlineRecord(union);
// Validate the entity
ValidationResult result = validator.validateOutput(validationDemo);
check(!result.isValid());
String errorMessages = result.getMessages().toString();
check(errorMessages.contains("/stringA :: length of \"stringA is readOnly\" is out of range 1...10"));
check(errorMessages.contains("/stringB :: field is required but not found"));
// Fix the entity
validationDemo.setStringA("abcd").setStringB("stringB");
// Validate the entity again
result = validator.validateOutput(validationDemo);
check(result.isValid());
return validationDemo;
}
@RestMethod.BatchGet
public Map<Integer, ValidationDemo> batchGet(Set<Integer> ids, @ValidatorParam RestLiDataValidator validator)
{
Map<Integer, ValidationDemo> resultMap = new HashMap<Integer, ValidationDemo>();
// Generate entities that are missing a required field
for (Integer id : ids)
{
ValidationDemo.UnionFieldWithInlineRecord union = new ValidationDemo.UnionFieldWithInlineRecord();
union.setMyRecord(new myRecord());
ValidationDemo validationDemo = new ValidationDemo().setStringA("a").setStringB("b").setUnionFieldWithInlineRecord(union);
resultMap.put(id, validationDemo);
};
// Validate outgoing data
for (ValidationDemo entity : resultMap.values())
{
ValidationResult result = validator.validateOutput(entity);
check(!result.isValid());
check(result.getMessages().toString().contains("/UnionFieldWithInlineRecord/com.linkedin.restli.examples.greetings.api.myRecord/foo1 :: field is required but not found"));
}
// Fix entities
for (Integer id : ids)
{
resultMap.get(id).getUnionFieldWithInlineRecord().getMyRecord().setFoo1(1234);
}
// Validate again
for (ValidationDemo entity : resultMap.values())
{
ValidationResult result = validator.validateOutput(entity);
check(result.isValid());
}
return resultMap;
}
@RestMethod.GetAll
public List<ValidationDemo> getAll(@ValidatorParam RestLiDataValidator validator)
{
List<ValidationDemo> validationDemos = new ArrayList<ValidationDemo>();
// Generate entities with stringA fields that are too long
for (int i = 0; i < 10; i++)
{
ValidationDemo.UnionFieldWithInlineRecord union = new ValidationDemo.UnionFieldWithInlineRecord();
union.setMyEnum(myEnum.FOOFOO);
validationDemos.add(new ValidationDemo().setStringA("This string is too long to pass validation.")
.setStringB("stringB").setUnionFieldWithInlineRecord(union));
}
// Validate outgoing data
for (ValidationDemo entity : validationDemos)
{
ValidationResult result = validator.validateOutput(entity);
check(!result.isValid());
check(result.getMessages().toString().contains("/stringA :: length of \"This string is too long to pass validation.\" is out of range 1...10"));
}
// Fix entities
for (ValidationDemo validationDemo : validationDemos)
{
validationDemo.setStringA("short str");
}
// Validate again
for (ValidationDemo entity : validationDemos)
{
ValidationResult result = validator.validateOutput(entity);
check(result.isValid());
}
return validationDemos;
}
@Finder("search")
public List<ValidationDemo> search(@QueryParam("intA") Integer intA, @ValidatorParam RestLiDataValidator validator)
{
List<ValidationDemo> validationDemos = new ArrayList<ValidationDemo>();
// Generate entities that are missing stringB fields
for (int i = 0; i < 3; i++)
{
ValidationDemo.UnionFieldWithInlineRecord union = new ValidationDemo.UnionFieldWithInlineRecord();
union.setMyEnum(myEnum.FOOFOO);
validationDemos.add(new ValidationDemo().setStringA("valueA").setIntA(intA).setUnionFieldWithInlineRecord(union));
}
// Validate outgoing data
for (ValidationDemo entity : validationDemos)
{
ValidationResult result = validator.validateOutput(entity);
check(!result.isValid());
check(result.getMessages().toString().contains("/stringB :: field is required but not found"));
}
// Fix entities
for (ValidationDemo validationDemo : validationDemos)
{
validationDemo.setStringB("valueB");
}
// Validate again
for (ValidationDemo entity : validationDemos)
{
ValidationResult result = validator.validateOutput(entity);
check(result.isValid());
}
return validationDemos;
}
}