I'm trying to use the jersey-client to make some RESTful requests with XML messages. I don't want to serve any endpoints so there are no jersey-server packages involved.
For the testing purposes I'm using the publicly reachable http://www.thomas-bayer.com/sqlrest/CUSTOMER testing service.
As stated in 9.2.4. Using Custom JAXBContext I have a custom ContextResolver class which is:
#Provider
#Produces({"application/xml"})
public class MyJaxbContextProvider implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBContext getContext(Class<?> type) {
if (context == null) {
try {
context = JAXBContext.newInstance("resttest.jaxb");
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
return context;
}
}
This ContextResolver is registered in the rest client with:
client = ClientBuilder.newClient().register(MoxyXmlFeature.class).register(MyJaxbContextProvider.class);
My Customer entity is :
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"id",
"firstname",
"lastname",
"street",
"city"
})
#XmlRootElement(name = "CUSTOMER")
public class Customer {
#XmlElement(name = "ID")
protected Integer id;
#XmlElement(name = "FIRSTNAME")
protected String firstName;
#XmlElement(name = "LASTNAME")
protected String lastName;
#XmlElement(name = "STREET")
protected String street;
#XmlElement(name = "CITY")
protected String city;
// getters and setters following
// ...
}
And finally the test class making the actual requests is:
public class RestClientTest {
private static Client client;
#BeforeClass
public static void beforeClass() {
client = ClientBuilder.newClient().register(MoxyXmlFeature.class).register(MyJaxbContextProvider.class);
}
#Test
public void testCreateCustomerWithEntity() { // Error
Customer customer = new Customer();
customer.setId(50);
customer.setFirstName("Nikol");
Response res = client.target("http://www.thomas-bayer.com/sqlrest/CUSTOMER/").request()
.post(entity(customer, MediaType.APPLICATION_XML_TYPE));
}
#Test
public void testGetCustomer() { // Error
Customer customer = client.target("http://www.thomas-bayer.com/sqlrest/CUSTOMER/3/").request()
.get(new GenericType<Customer>() {});
assertThat(customer.getId(), equalTo(3));
}
}
I have packed these files in a resttest project at https://github.com/georgeyanev/resttest
After cloning the tests can be executed simply with
mvn test
I expect when I'm making a POST requests and passing a Customer instance the latter to be marshalled by the jersey client (testCreateCustomerWithEntity).
And when I'm making a GET request the returned Customer entity to be unmarshalled (testGetCustomer).
But both tests fail with MessageBodyProviderNotFoundException saying that there is no MessageBodyWriter/MessageBodyReader found for media type application/xml and type Customer.
I'm using 2.19 version of both jersey-client and jersey-media-moxy libraries with oracle java 1.8.0_25
What could be the possible reason for this?
It appears that an additional dependency for jersey-media-jaxb is needed in order for the custom ContextResolver to be picked by jersey. Then the standard JAXB mechanisms are used to define the JAXBContextFactory from which a JAXBContext instance would be obtained.
In this case the JAXBContextFactory class is specified in jaxb.properties file in resttest.jaxb package.
Related
I am having the typical 'unexpected element (uri:"", local:"stickynote")' JAXB error, although it only shows when I set the event handler on the unmarshaller object. When the event handler is not set code runs fine, but the specific data needed is not unmarshalled.
I have worked on the problem for a couple of days, including many Internet searches, and am not hitting on the right solution. I feel like I am missing something simple, but so far it is eluding me.
Notes:
I am using Java JDK 8 with Apache NetBeans 15.
The original code was generated with the xjc command from schemas we receive from multiple vendors. I cannot modify the schemas and have to figure out how to use them "as is".
I wrote the code below to duplicate the issue and simplify the posted code. Import statements have been removed for brevity. If they are needed please let me know.
Since inheritance is involved in one of the classes, I added the appropriate #XmlSeeAlso annotation. That did not help.
This is my first real foray into JAXB. It seems to be perfect for this project. I just have to figure out how to get it to work.
First, example XML:
<?xml version="1.0" encoding="UTF-8"?>
<documents>
<document>
<notes>
<stickynote id="123">test note</stickynote>
</notes>
</document>
</documents>
Code to create the JAXB context and create the Unmarshaller object:
JAXBContext context = JAXBContext.newInstance( Documents.class );
Unmarshaller u = context.createUnmarshaller();
u.setEventHandler(new DefaultValidationEventHandler());
JAXBElement< Documents > root =
u.unmarshal(
new StreamSource( new File( FILENAME )),
Documents.class );
The corresponding classes to handle each element:
Documents class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "document" })
#XmlRootElement(name = "documents", namespace = "http://www.example.org/documents")
public class Documents {
protected Document document;
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
}
Document class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "notes" })
#XmlRootElement(name = "document", namespace = "http://www.example.org/documents")
public class Document {
protected NotesType notes;
public NotesType getNotes() {
return notes;
}
public void setNotes(NotesType notes) {
this.notes = notes;
}
}
NotesType class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "NotesType", propOrder = { "notes" })
public class NotesType {
protected List<NoteType> notes;
public List<NoteType> getNotes() {
if ( isNull( notes )) {
notes = new ArrayList<>();
}
return this.notes;
}
}
NoteType class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "NoteType", propOrder = { "note" })
#XmlSeeAlso({ StickyNote.class })
public class NoteType {
#XmlAttribute(name = "id", required = true)
protected String id;
protected String note;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
And finally, the StickyNote class, which extends the NoteType class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "subType" })
public class StickyNote extends NoteType {
protected String subType = "sticky";
public String getSubType() {
return subType;
}
public void setSubType(String subType) {
this.subType = subType;
}
}
The exception is:
DefaultValidationEventHandler: [ERROR]: unexpected element (uri:"", local:"stickynote"). Expected elements are <{}notes>
Location: line 5 of file:/C:/Test/documents.xml
javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"stickynote"). Expected elements are <{}notes>
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:726)
*and so on*
Thanks in advance for any and all suggestions and help!
john
My issue turned out to be how I was using xjc. I was building each schema we received individually, not realizing there was some overlap in the schemas due to the vendors using other vendors schemas. This caused issues when one vendor's schema was built early in the process and then another vendor's schema was built after it that used the earlier vendor's schema.
Building all the schemas as a group, instead of individually, allowed xjc to "see" everything it needed to see and the corresponding code worked as expected.
I have a SDR project and I am successfully validating the user entity for POST request but as soon as I update an existing entity using either PATCH or PUT the DB is updated BEFORE the validation is executed (the validator is being executed and error is returned but the DB is being updated anyway).
Do I need to setup a separate config for update ? Am I missing an extra step for that?
Entity
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Member {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_id_gen")
#SequenceGenerator(name = "member_id_gen", sequenceName = "member_id_seq")
#Id
#JsonIgnore
private long id;
#Version
private Integer version;
#NotNull
protected String firstName;
#NotNull
protected String lastName;
#Valid
protected String email;
}
Repository
#RepositoryRestResource(collectionResourceRel = "members", path = "member")
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
public Member findByFirstName(String firstName);
public Member findByLastName(String lastName);
}
Validator
#Component
public class BeforeSaveMemberValidator implements Validator {
public BeforeSaveMemberValidator() {}
private String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
#Override
public boolean supports(Class<?> clazz) {
return Member.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
if(ObjectUtils.isEmpty(member.getFirstName())) {
errors.rejectValue("firstName", "member.firstName.empty");
}
if(ObjectUtils.isEmpty(member.getLastName())) {
errors.rejectValue("lastName", "member.lastName.empty");
}
if(!ObjectUtils.isEmpty(member.getDni()) && !member.getDni().matches("^[a-zA-Z0-9]*$")) {
errors.rejectValue("dni", "member.dni.invalid");
}
if(!ObjectUtils.isEmpty(member.getEmail()) && !member.getEmail().matches(EMAIL_REGEX)) {
errors.rejectValue("email", "member.email.notValid");
}
}
}
BeforeSave service
#Service
#RepositoryEventHandler(Member.class)
public class MemberService {
#HandleBeforeCreate
#HandleBeforeSave
#Transactional
public void beforeCreate(Member member) {
...
}
}
I think you should rename your validator, for example, to MemberValidator then assign it as described here:
#Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new MemberValidator());
v.addValidator("beforeSave", new MemberValidator());
}
But I suggest you to use Bean validation instead of your custom validators. To use it in SDR project you can inject LocalValidatorFactoryBean, then assign it for 'beforeCreate' and 'beforeSave' events in configureValidatingRepositoryEventListener:
#Configuration
#RequiredArgsConstructor // Lombok annotation
public class RepoRestConfig extends RepositoryRestConfigurerAdapter {
#NonNull private final LocalValidatorFactoryBean validatorFactoryBean;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", validatorFactoryBean);
v.addValidator("beforeSave", validatorFactoryBean);
super.configureValidatingRepositoryEventListener(v);
}
}
In this case your SDR will automatically validate payloads of POST, PUT and PATCH requests for all exposed SDR repositories.
See my example for more details.
I'm (new in spring development) creating REST API for my application, CRUD operations are implemented successfully but now I want to implement server side validation. I've also read that there are several ways through which validation could be implemented.
Using given annotations -> #notempty, #email, etc...
Using custom validation -> extending validators
I want to implement both of them in my application, With reference to that,
is it good approach to follow?
OR
Is there any other ways through which validation can be implemented?
Controller
#RestController
public class EmployeeController {
#Autowired
DataServices dataServices;
#Autowired
EmployeeValidator employeeValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(employeeValidator);
}
#RequestMapping(value = "/employee/", method = RequestMethod.POST)
public ResponseEntity<Object> createUser(
#Valid #RequestBody Employee employee,
UriComponentsBuilder ucBuilder) throws Exception,
DataIntegrityViolationException {
if (dataServices.addEmployee(employee) == 0) {
Error error = new Error(1, "Data integrity violation",
"Email id is already exists.");
return new ResponseEntity<Object>(error, HttpStatus.CONFLICT);
}
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/employee/{id}")
.buildAndExpand(employee.getId()).toUri());
Status status = new Status(1, "Employee has been added successfully.");
return new ResponseEntity<Object>(status, headers, HttpStatus.CREATED);
}
}
Error Handler
#ControllerAdvice
public class RestErrorHandler {
private static final Logger logger = Logger
.getLogger(RestErrorHandler.class);
private MessageSource messageSource;
#Autowired
public RestErrorHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDTO processValidationError(
MethodArgumentNotValidException ex) {
logger.debug("Handling form validation error");
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getField(), localizedErrorMessage,
fieldError.getDefaultMessage());
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError,
currentLocale);
// If a message was not found, return the most accurate field error code
// instead.
// You can remove this check if you prefer to get the default error
// message.
if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
String[] fieldErrorCodes = fieldError.getCodes();
localizedErrorMessage = fieldErrorCodes[0];
}
return localizedErrorMessage;
}
}
Validator
#Component
public class EmployeeValidator implements Validator {
public boolean supports(Class clazz) {
return Employee.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", errors
.getFieldError().getCode(), "First name is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", errors
.getFieldError().getCode(),
"Last name is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", errors
.getFieldError().getCode(),
"Email is required.");
}
}
Model
#Entity
#Table(name = "employee")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "id")
private long id;
// #NotEmpty(message = "Please enter first name")
#Column(name = "first_name")
private String firstName;
// #NotEmpty(message = "Please enter last name")
#Column(name = "last_name")
private String lastName;
// #NotEmpty(message = "Please enter email address")
#Email(message = "Please enter valid email address")
#Column(name = "email", unique = true)
private String email;
#NotEmpty(message = "Please enter mobile number")
#Size(min = 10, message = "Please enter valid mobile number")
#Column(name = "phone")
private String phone;
//Getter and Setter
}
In your aproach you are using Server side validations but only in the controller layer. Have you tryied to use Bussines layer validations, like Hibernate Validation API http://hibernate.org/validator/
I've used it in a recent project and form me it's a great way to keep data consistent. Some tweaks and utils were needed to make it work as we wanted but it was not too difficult. For example, this validations, by default, are only checked just after persisting a Object in database, but in our controller we needed to make this validations earlier, so you we had to implement a way to call validation mechanism that relies on hibernate validation mechanism. Or, as another example, we had to develop a similar system on a web service to return errors when incoming data was not valid.
One way to use validations when needed is to implement it on all your bussines objects. They can inherit for a class like this:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.xml.bind.annotation.XmlTransient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.fasterxml.jackson.annotation.JsonIgnore;
public abstract class BusinessObject implements Serializable, IObjectWithReport, IBusinessObject {
private static Log log = LogFactory.getLog(BusinessObject.class.getName());
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
#JsonIgnore
private Set<ConstraintViolation<BusinessObject>> errors;
/* Validation methods */
public final boolean valid() {
preValidate();
errors = new HashSet<ConstraintViolation<BusinessObject>>();
errors = validator.validate(this);
postValidate();
return errors.isEmpty();
}
/**
* Method to be overwriten in subclases so any BO can make some arrangement before checking valid
*/
protected void preValidate() {
log.trace("Generic prevalidate of " + this.getClass().getName());
}
/**
* Method to be overwriten in subclases so any BO can make some arrangement once validation has been made
*/
protected void postValidate() {
log.trace("Generic postValidate of " + this.getClass().getName());
}
public Set<ConstraintViolation<BusinessObject>> getErrors() {
return errors;
}
public boolean hasErrors() {
return errors != null && !errors.isEmpty();
}
}
Note that i use standard javax.validation.Validation API (check references here JPA 2.0 : what is javax.validation.* package?). But the implementation i use is the one from Hibernate.
Pros:
Validations are placed in one single layer, not spread along various layers. So they are easier to maintain.
Better model consistency because of that data is always validated in the same way, independently of how it was generated (user input, web service, pulled from other systems, etc).
Cons:
You need to develop some utils so you can use Model Validations in other layers, but it's not very dificult.
May be overkill if you have a simple project, whithout complexities like many info sources (user input, webservices input, rest services, other database systemas, etc) or interactions.
I am trying to implement a REST service using Spring 4.
The application is built using Java 7 and runs on Tomcat 7.
The REST method will return a customer object in JSON. The application is annotation-driven.
The Customer class has JAXB annotations.
Jackson jars are present in the class path. As per my understanding Jackson will use JAXB annotations to generate the JSON.
The Customer Class :
#XmlRootElement(name = "customer")
public class Customer {
private int id;
private String name;
private List favBookList;
#XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlElementWrapper(name = "booklist")
#XmlElement(name="book")
public List getFavBookList() {
return favBookList;
}
public void setFavBookList(List favBookList) {
this.favBookList = favBookList;
}
}
I have annotated the REST service class as #RestController (as per Spring 4)
The REST method to return a customer object in JSON:
#RequestMapping(value="/customer.json",produces="application/json")
public Customer getCustomerInJSON(){
Customer customerObj = new Customer();
customerObj.setId(1);
customerObj.setName("Vijay");
ArrayList<String> favBookList = new ArrayList<String>();
favBookList.add("Book1");
favBookList.add("Book2");
customerObj.setFavBookList(favBookList);
return customerObj;
}
The result I expected, when I hit the URL :
{"id":1,"booklist":{"book":["Book1","Book2"]},"name":"Vijay"}
What I get:
{"id":1,"name":"Vijay","favBookList":["Book1","Book2"]}
It seems Jackson is ignoring the JAXB annotations #XmlElementWrapper(name = "booklist") and
#XmlElement(name="book") above getFavBookList() method in Customer class
Am I missing something?
Need guidance. Thanks.
Basically the point is, you have given xml annotations and are expecting Json output.
You need to find out Json equivalent for its xml counter part #xmlElementWrapper.
This feature used to work in jackson 1.x but does not in Jackson 2.x
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Item", propOrder = {
"code",
"name",
"price"
})
#XmlRootElement(name="inventory")
public class Item {
#XmlElement(name="catalog_num", required = true)
protected String code;
#XmlElement(name="catalog_descrip", required = true)
protected String name;
#XmlElement(name="prod_price")
protected double price;
public String getCode() {
return code;
}
JAXBContext databaseJC = JAXBContext.newInstance(Item.class);
Unmarshaller databaseUnmarshaller = databaseJC.createUnmarshaller();
File databaseXML = new File("src/forum6838882/database.xml");
Item item = (Item) databaseUnmarshaller.unmarshal(databaseXML);
My question is:
How could I get the #XmlElement(name="catalog_num", required = true) from item object. I need know the name="catalog_num" here.
JAXB (JSR-222) does not provide an API to introspect the metadata. You can however use the Java Reflection APIs (java.lang.reflect) to get the annotations and examine them yourself.
Demo
import java.lang.reflect.*;
import javax.xml.bind.annotation.*;
public class Demo {
public static void main(String[] args) throws Exception {
Field field = Item.class.getDeclaredField("code");
XmlElement xmlElement = field.getAnnotation(XmlElement.class);
System.out.println(xmlElement.name());
}
}
Output
catalog_num