im trying to retrieve a get response from IMDB.com's or AnimeNewsNetwork's APIs using java EE
i started hacking this guy's really nice example of connecting to a weather website API (had to install vaadin view framework)
it looks like i've made some progress (especially in understanding what's going on) but im at a problem for a long time now (3 hours although a lot of that was failing to get github to work....)
i am getting, when i try to run the application:
javax.servlet.ServletException: com.vaadin.server.ServiceException: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html; charset=utf-8, type=class org.example.domain.ForecastResponse, genericType=class org.example.domain.ForecastResponse.
i pasted
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.15</version>
</dependency>
into the pom thing and i found a button in netbeans that "checks for problems" then it automatically updated my project with dependencies which took ages and was rather shocking and i thought it'd work then
then i tried changing .request(MediaType.TEXT_HTML) back and forth to APPLICATION_JSON (imdb defaults to json) and TEXT_HTML_TYPE or whatever i tried.
lastly i tried changing my query input from a correct movie ID tt1285016 to an incorrect one to see if that would give a different result when it only returns 2 parameters instead of 20. this brings me to the question: how does my application know how to map the response from the API to the class? a normal response is a JSON string that has 20 fields so how does my application know where to put them? maybe this is the missing link. i just changed the movie classes attributes to Response and Error to match the 2 returns from sending an incorrect movie ID to the API but still same problem
(hours later): tried to run using the anime news network's API instead and getting similar error which differs in saying "messagbodyreader not found" for text/xml instead of text/html...
i've also tried adding a few more things to the pom file from jersey dependencies document and well one of them makes my project stop deploying even after i delete and rebuild it so i've had fun with that.
below i will paste the code i am using. please note i am just hacking this guy's example up trying to get my basic functionality working and avoiding changing anything else so i can understand what i'm supposed to do, before rewriting it from scratch just to prototype before beginning to plan the actual project!! i have a lot more tutorials/examples to look at in the future so don't worry about my code other than the problem i'm stuck on :/
this is the "view" java class that uses vaadin
VaadinUI.java
package org.example;
import com.vaadin.annotations.Theme;
import com.vaadin.cdi.CDIUI;
import com.vaadin.data.Property;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.UI;
import javax.inject.Inject;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
* A simple example how to consume REST apis with JAX-RS and display that in
* a Vaadin UI.
*/
#CDIUI("")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
JsonService service;
ForecastDisplay display = new ForecastDisplay();
NativeSelect citySelector = new NativeSelect("Choose city");
#Override
protected void init(VaadinRequest request) {
// citySelector.addItems("tt1285016","tt1285016");
// citySelector.addValueChangeListener(this::changeCity);
// citySelector.setValue("tt1285016");
setContent(new MVerticalLayout(
new Header("Simple REST weather"),
display));
display.setForecast(service.fetchMovie());
}
//// display.setForecast(service.getForecast(citySelector.getValue().toString()));
// }
}
JsonService.java
package org.example;
import com.vaadin.annotations.Theme;
import com.vaadin.cdi.CDIUI;
import com.vaadin.data.Property;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.UI;
import javax.inject.Inject;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
* A simple example how to consume REST apis with JAX-RS and display that in
* a Vaadin UI.
*/
#CDIUI("")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
JsonService service;
ForecastDisplay display = new ForecastDisplay();
NativeSelect citySelector = new NativeSelect("Choose city");
#Override
protected void init(VaadinRequest request) {
// citySelector.addItems("tt1285016","tt1285016");
// citySelector.addValueChangeListener(this::changeCity);
// citySelector.setValue("tt1285016");
setContent(new MVerticalLayout(
new Header("Simple REST weather"),
display));
display.setForecast(service.fetchMovie());
}
//// display.setForecast(service.getForecast(citySelector.getValue().toString()));
// }
}
ForecastDisplay.java
package org.example;
import java.util.Calendar;
import java.util.Date;
import org.example.domain.Forecast;
import org.example.domain.ForecastResponse;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.label.RichText;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
*
* #author Matti Tahvonen <matti#vaadin.com>
*/
public class ForecastDisplay extends MVerticalLayout {
String mainTemplate = "Tomorrow in there will be ";
public ForecastDisplay() {
}
public void setForecast(ForecastResponse fr) {
// removeAllComponents();
removeAllComponents();
addComponents(
new Header(String.format(mainTemplate,fr.getMovie().getMovieInfo(),
fr.getMovie().getMovieInfo()))
);
//
// for (Forecast f : fr.getList()) {
// cal.add(Calendar.DAY_OF_MONTH, 1);
// Date date = cal.getTime();
// Double temperature = f.getTemp().getDay();
// String desc = f.getWeather().get(0).getDescription();
// String md = String.format(detailTemplate, date, temperature, desc);
// addComponent(new RichText().withMarkDown(md));
}
}
ForecastResponse.java class
package org.example.domain;
import java.util.ArrayList;
public class ForecastResponse {
// private String cod;
// private Double message;
// private City city;
// private Integer cnt;
// private java.util.List<Forecast> list = new ArrayList<>();
private Movie movie;
//
// public String getCod() {
// return cod;
// }
//
// public void setCod(String cod) {
// this.cod = cod;
// }
//
// public Double getMessage() {
// return message;
// }
//
// public void setMessage(Double message) {
// this.message = message;
// }
//
// public City getCity() {
// return city;
// }
//
// public void setCity(City city) {
// this.city = city;
// }
public Movie getMovie()
{
return movie;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
//
// public Integer getCnt() {
// return cnt;
// }
//
// public void setCnt(Integer cnt) {
// this.cnt = cnt;
// }
//
// public java.util.List<Forecast> getList() {
// return list;
// }
//
// public void setList(java.util.List<Forecast> list) {
// this.list = list;
// }
}
Movie.java class
package org.example.domain;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Generated;
public class Movie {
private String Response;
private String Error;
public String getMovieInfo() {
return Response;
}
public void setMovieInfo(String name) {
this.Response = Response;
}
public String getMovieInfo2() {
return Error;
}
public void setMovieInfo2(String name) {
this.Error = Error;
}
}
the guy's project before i cut it up (you need vaadin plugin to run it)
https://github.com/mstahv/consuming-rest-apis
just to reiterate i am getting
javax.servlet.ServletException: com.vaadin.server.ServiceException: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html; charset=utf-8, type=class org.example.domain.ForecastResponse, genericType=class org.example.domain.ForecastResponse.
when i try to run the application and have tried a number of solutions... but i think its because of something i changed because i had no problems getting the guy's original files to run
i have uploaded my current version to mirrorcreator (can't get git to work...sigh) : https://www.mirrorcreator.com/files/KQF7SBBH/restapiexampletest1zip._links
thankyou for reading . i have searched google a lot but not able to fix it using the suggestions like change the poms file or whatever....
Related
I want to create a website/API, that reads in a csv and returns the wanted resoureces. I use SpringBoot: the Spring Web dependency. For reading in the csv I import implementation('com.opencsv:opencsv:5.6') to dependencies in my build.gradle-file. I decided to use the following structure:
Four java-files in src\main\java\com\example\so:
The bean Car.java:
package com.example.so;
import com.opencsv.bean.CsvBindByName;
import java.math.BigDecimal;
public class Car {
#CsvBindByName
private int id;
#CsvBindByName(column = "name")
private String brand;
#CsvBindByName
private BigDecimal price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
#Override
public String toString() {
return "Car{" +
"id=" + id +
", brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
To display the correct car I use CarController.java:
package com.example.so;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class CarController {
#GetMapping("/car/{id}")
public Car findcar(#PathVariable int id) {
for (Car car: ReadInCSV.cars){
if (car.getId()==id){
System.out.println(car.toString());
return car;
}
}
return null;
}
#GetMapping("")
public String findcar() {
return "Hi!";
}
}
To read in the csv-file ReadInCSV.java:
package com.example.so;
import com.opencsv.bean.CsvToBeanBuilder;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.List;
public class ReadInCSV {
static final String fileName="src/main/resources/static/abc.csv";
static List<Car> cars;
static {
try {
cars= new CsvToBeanBuilder(new FileReader(fileName)).withType(Car.class).build().parse();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
and to start the webservice SoApplication.java:
package com.example.so;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class SoApplication {
public static void main(String[] args) {
SpringApplication.run(SoApplication.class, args);
}
}
The file with the data abc.csv:
id,name,price
1,Audi,52642
2,Mercedes,57127
3,Skoda,9000
4,Volvo,29000
5,Bentley,350000
6,Citroën,21000
7,Füll,41400
8,Rosé,21600
9,Toyota,26700
It works more or less fine, but when you enter http://localhost:8080/car/6, my browser (Firefox) displays "Citroën" instead of "Citroën". Reading in the csv seems to work fine, because when you print the bean using its .toString() you get Car{id=6, brand='Citroën', price=21000}. So apparently the json-conversion is the problem. What can I do to solve this issue?
I am new to world of java-web, so feel free telling me if there are some other problems with my approach.
I don't think this is a problem with JSON conversion but with your character encoding. Make sure both your CSV file and the output to JSON are in the same encoding, it's probably best to use UTF8. If you explicitly set produces = APPLICATION_JSON_VALUE on your controller annotation, it should already use UTF8 for that. So if it then still fails, the question remains whether the CSV is in UTF8.
Also, if you don't know what I'm talking about, https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ is pretty much a must-read.
I have this piece of code:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
protected Date statusDate;
But for somehow it accepts date formats like -
"statusDate": "2017-13-27"
or
"statusDate": "201823-12-12"
Is it possible to validate the format within the request (not manually)?
#JsonFormat is used to set the output format when you're returning the statusDate as response.
It is better you create a DTO object that will accept String statusDate and then convert it to Date format in your controller.
To validate the date in String format, you can use #Pattern
public class StatusDateDto {
#NotNull(message="Status date is a required field")
#Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}", message="Invalid status date")
private String statusDate;
//Getter and setter
}
public ResponseEntity<?> postStatusDate(#Valid #RequestBody StatusDateDto dateDto, BindingResult result) {
if (result.hasFieldErrors()) {
String errors = result.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
// Convert the String to Date after validation
return ResponseEntity.ok().build();
}
Yes, you can. Let me show u the details.
First, pls create a base controller to filter all the requests like below:
package com.letv.address.controller;
import com.letv.address.utils.ConstantCode;
import com.letv.address.utils.ResponseWrapper;
import com.letv.xice.core.controller.GlobalController;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import java.util.ArrayList;
import java.util.List;
/**
* Created by shichaoyang on 2017/1/10.
*/
public class BaseController extends GlobalController {
public ResponseWrapper requestCheckAndPost(BindingResult result) {
if (result.hasErrors()) {
List<Object> errorList = new ArrayList<>();
StringBuilder sBuilder = new StringBuilder();
for (ObjectError error : result.getAllErrors()) {
String fieldName = ((FieldError) error).getField();
String fieldMessage = error.getDefaultMessage();
sBuilder.append(fieldName)
.append(" ")
.append(getMessage(fieldMessage))
.append(";");
errorList.add(fieldName);
}
return new ResponseWrapper(
ConstantCode.FAILING_WITH_ERROR_PARAM_CODE
, errorList.toArray()
, ""
, sBuilder.toString()
);
}
return null;
}
}
From above code, the BindingResult will check the #JsonFormat or other components header, like #NotBlank, #Pattern and so on. If they hit the rule, they will be caught by the BindingResult and we can get the error. Below is the DTO object I used, just show it to u so that you can get more details:
package com.letv.address.controller.dto;
import com.letv.address.utils.ConstantCode;
import org.hibernate.validator.constraints.NotBlank;
/**
* Created by shichaoyang on 2016/12/23.
*/
public class ChildrenAreaSelectRequest{
#NotBlank(message = ConstantCode.REQUEST_VALIDATE_NOT_EMPTY)
private String areaIds;
public String getAreaIds() {
return areaIds;
}
public void setAreaIds(String areaIds) {
this.areaIds = areaIds;
}
}
Then in our business logic controller, we need to extend the base controller and write the codes like below:
package com.letv.address.controller;
import com.letv.address.controller.dto.ChildrenAreaSelectRequest;
import com.letv.address.controller.dto.ParentAreaSelectReqeust;
import com.letv.address.domain.Area;
import com.letv.address.service.ChildAreaService;
import com.letv.address.utils.ConstantCode;
import com.letv.address.utils.ResponseWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* Created by shichaoyang on 2016/12/12.
*/
#RestController("areaController")
public class AreaController extends BaseController {
#Autowired
protected ChildAreaService childAreaService;
/**
* get area info by parent id
*
* #param areaReqeust
* #param result
*/
#ResponseBody
#RequestMapping(value = ConstantCode.CHILD_AREA_PATH, method = {RequestMethod.POST})
public ResponseWrapper childArea(#RequestBody #Valid ParentAreaSelectReqeust areaReqeust, BindingResult result) {
ResponseWrapper validationWrapper = requestCheckAndPost(result);
if (validationWrapper != null) {
return validationWrapper;
}
List<Area> areaList = childAreaService.selectByParentId(areaReqeust.getParentId());
if (areaList == null || areaList.size() == 0) {
return new ResponseWrapper(ConstantCode.SUCCESS_WITH_EMPTY_DATA_CODE, new ArrayList<>());
} else {
return new ResponseWrapper(ConstantCode.SUCCESS_WITH_FILL_DATA_CODE, areaList);
}
}
}
By using above method, you can validate the field easily within the request. It's a beautiful way to achive this.
Hope that helps.
EDIT:
Replace the images with real codes so that anyone needs to test it.
Well, you need to write a custom date serialisation/de-serialisation class and throw a custom exception if the date format you receive while intercepting the data is not what you'd expected.
This answer will point you in the right direction of how to do so.
Also you can have the validator in your code to validate the payload and leave Jackson to it's simple state.
I was reading the Spring docs and found that creating a subclass from ResponseEntityExceptionHandler was a good way on handling exceptions. However, I tried to handle exceptions in a different way, since I need to diff BusinessExceptions from TechnicalExceptions.
Created a bean called BusinessFault which encapsulates the exception details:
BusinessFault.java
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonInclude(value = Include.NON_NULL)
public class BusinessFault {
#JsonProperty(value = "category")
private final String CATEGORY = "Business Failure";
protected String type;
protected String code;
protected String reason;
protected String description;
protected String instruction;
public BusinessFault(String type, String code, String reason) {
this.type = type;
this.code = code;
this.reason = reason;
}
public BusinessFault(String type, String code, String reason, String description, String instruction) {
this.type = type;
this.code = code;
this.reason = reason;
this.description = description;
this.instruction = instruction;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getInstruction() {
return instruction;
}
public void setInstruction(String instruction) {
this.instruction = instruction;
}
public String getCATEGORY() {
return CATEGORY;
}
}
Created a BusinessException class, which do the job by creating a BusinessFault beans through the details passed by its constructor:
BusinessException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
public abstract class BusinessException extends RuntimeException {
private BusinessFault businessFault;
public BusinessException(String type, String code, String reason) {
this.businessFault = new BusinessFault(type, code, reason);
}
public BusinessException(String type, String code, String reason, String description, String instruction) {
this.businessFault = new BusinessFault(type, code, reason, description, instruction);
}
public BusinessException(BusinessFault businessFault) {
this.businessFault = businessFault;
}
public BusinessFault getBusinessFault() {
return businessFault;
}
public void setBusinessFault(BusinessFault businessFault) {
this.businessFault = businessFault;
}
}
Created a specific UserNotFoundException class, which extends from BusinessException class:
UserNotFoundException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
import com.rest.restwebservices.exception.map.ExceptionMap;
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(BusinessFault businessFault) {
super(businessFault);
}
public UserNotFoundException(String reason) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason);
}
public UserNotFoundException(String reason, String description, String instruction) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason, description,
instruction);
}
}
Created a BusinessExceptionHandler, but instead of being a subclass of ResponseEntityExceptionHandler, it's only has a #ControllerAdvice annotation and a method that handles all thrown BusinessExceptions:
BusinessExceptionHandler.java
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.BusinessException;
import com.rest.restwebservices.exception.fault.BusinessFault;
#ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
#ExceptionHandler(BusinessException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, BusinessException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.OK);
}
}
The service layer can throw a UserNotFoundException:
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public User findById(Long id) {
User user = userRepository.findOne(id);
if (user == null)
throw new UserNotFoundException("The ID " + id + " doesn't behave to any user!");
return user;
}
}
It works fine. But I was wondering if this is a bad practice on handling exceptions?
I've got a little problem with your exception handling. Principally it is absolutely ok to catch runtime exceptions, handle them and send them forth to the client, which is probably someone using your REST service and getting the error response as a JSON object. If you manage to tell him what he did wrong and what he can do about it, great! Of course, it will add some complexity to it, but it is probably easy and comfortable to work with that API.
But think about the backend developers, too, that work with your code. Especially the public User findById(Long id) method in your UserService is obscure. The reason for this is that you made your BusinessException, in particular, the UserNotFoundException unchecked.
If I joined your (backend) team, and I was to write some business logic using that service, I'd be quite sure what I had to expect from that method: I pass a user ID and get back a User object if it was found or null if not. That's why I would write code like that
User user = userService.findById("42A");
if (user == null) {
// create a User or return an error or null or whatever
} else {
// proceed
}
However, I would never know, that the first condition will never be true since you never return null. How should I know that I had to catch an Exception?
Is the compiler telling me to catch it? No, as it is not checked.
Would I look into your source code? Hell, no! Your case is extremely simple. That UserNotFoundException may be raised in another method in another class among hundred lines of code. Sometimes I couldn't look inside it, anyway, as that UserService is just a compiled class in a dependency.
Do I read the JavaDoc? Hahaha. Let's say, 50% of the time I wouldn't, and the other 50% you've forgotten to document it, anyway.
So, the developer has to wait until his code is used (either by a client or in Unit tests) to see that it doesn't work as he intended, forcing him to redesign what he has coded so far. And if your whole API is designed that way, that unchecked exceptions pop out of nowhere, it can be very very annoying, it costs time and money and is so easy to avoid, actually.
I use a similar way to handle exceptions. But in my case, different handlers are managed according to the error status (e.g. an user exists, an user cannot be registered due to some unsatisfied condition, etc.).
You also might add your generic BusinessException for some special cases. Hope it helps you feel better.
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.ResourceNotFoundException;
import com.rest.restwebservices.exception.PreconditionFailedException;
import com.rest.restwebservices.exception.ResourceAlreadyExistsException;
import com.rest.restwebservices.exception.fault.BusinessFault;
#ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceNotFoundException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.NOT_FOUND);
}
#ExceptionHandler(PreconditionFailedException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, PreconditionFailedExceptionex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.PRECONDITION_FAILED);
}
#ExceptionHandler(ResourceAlreadyExistsException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceAlreadyExistsException) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.CONFLICT);
}
}
I am implementing restful API where i would like the caller to specify the specific properties that one likes to be serialized something like /dogs/1?fields=name,breed. So if the dog has many more properties other than name and breed and I am using a standard serialization library like jackson how can i tell jackson to ignore the rest of the properties while serializing in runtime?
In other words what is the way to tell jackson to dynamically ignore some properties at runtime?
You did not specify version of Jackson library, so I assume that you are using 2.2.0 or newest. In Jackson this is not easy task to do but we can do it, for example, with BasicClassIntrospector. Below source code shows how it should look like. For example we have one POJO class:
class User {
private String name;
private String surname;
// getters/setters
}
We have to define new BasicClassIntrospector which returns bean definition object only with this fields which we need:
class ExclusionAnnotationIntrospector extends BasicClassIntrospector {
private Collection<String> properties;
public ExclusionAnnotationIntrospector(Collection<String> properties) {
this.properties = properties;
}
#Override
public BasicBeanDescription forSerialization(SerializationConfig serializationConfig,
JavaType javaType, MixInResolver mixInResolver) {
BasicBeanDescription beanDesc = super.forSerialization(serializationConfig, javaType,
mixInResolver);
deleteUnwantedProperties(beanDesc);
return beanDesc;
}
private void deleteUnwantedProperties(BasicBeanDescription beanDesc) {
List<BeanPropertyDefinition> beanProperties = new ArrayList<BeanPropertyDefinition>(
beanDesc.findProperties());
for (BeanPropertyDefinition bpd : beanProperties) {
String name = bpd.getName();
if (!properties.contains(name)) {
beanDesc.removeProperty(name);
}
}
}
}
Now, we have to create new ObjectMapper class with above introspector. We have to define properties we need in constructor:
class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper(Collection<String> properties) {
this._serializationConfig = getSerializationConfig().with(
new ExclusionAnnotationIntrospector(properties));
}
}
And, finally, example usage:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription;
import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("Tom");
user.setSurname("Garnier");
CustomObjectMapper onlyNameSerializer = new CustomObjectMapper(toSet("name"));
CustomObjectMapper onlySurnameSerializer = new CustomObjectMapper(toSet("surname"));
CustomObjectMapper fullUserSerializer = new CustomObjectMapper(toSet("name", "surname"));
System.out.println(onlyNameSerializer.writeValueAsString(user));
System.out.println(onlySurnameSerializer.writeValueAsString(user));
System.out.println(fullUserSerializer.writeValueAsString(user));
}
private static Set<String> toSet(String... params) {
HashSet<String> set = new HashSet<String>();
for (String item : params) {
set.add(item);
}
return set;
}
}
Above program prints:
{"name":"Tom"}
{"surname":"Garnier"}
{"name":"Tom","surname":"Garnier"}
I'd recommend to take a look at http://flexjson.sourceforge.net/
(in particular section Working With Includes/Excludes)
Example:
public String toJsonWithIncludes(Object obj, String...includes) {
return new JSONSerializer()
.include(includes)
.exclude("data.*","*.class")
.serialize(obj);
}
...
toJsonWithIncludes(obj, "name","breed");
I generated my client side stubs for a NetBeans webservice.
The webservice implementation uses a local POJO from my project. The generated stubs have created a revision of this POJO for use. When I'm using the service, I want to use the original POJO, not the generated type..? Type casting doesn't work.
i.e. (note the packages)
package adiib.ws.harmoniser;
#WebMethod(operationName = "getStartupLogMessages")
public ArrayList<LogMessage> getStartupLogMessages() {
return startupLogMessages;
}
The POJO LogMessage reads:
package adiib.shared;
public class LogMessage implements Serializable
{
private static final long serialVersionUID = 8379681391654158512L;
private String exceptionMessage;
private String customMessage;
private String stackTrace;
private LogMessageEnum classification;
private String effectiveTime;
private String exceptionClassName;
private String throwerClassName;
public LogMessage(){}
public LogMessage(String exceptionMessage, String customMessage,
String stackTrace, LogMessageEnum classification, String effectiveTime,
String exceptionClassName, String throwerClassName)
{
this.exceptionMessage = exceptionMessage;
this.customMessage = customMessage;
this.stackTrace = stackTrace;
this.classification = classification;
this.effectiveTime = effectiveTime;
this.exceptionClassName = exceptionClassName;
this.throwerClassName = throwerClassName;
}
public String getCustomMessage() {
return customMessage;
}
public void setCustomMessage(String customMessage) {
this.customMessage = customMessage;
}
public String getExceptionMessage() {
return exceptionMessage;
}
public void setExceptionMessage(String exceptionMessage) {
this.exceptionMessage = exceptionMessage;
}
public LogMessageEnum getClassification() {
return classification;
}
public void setClassification(LogMessageEnum classification) {
this.classification = classification;
}
public String getEffectiveTime() {
return effectiveTime;
}
public void setEffectiveTime(String effectiveTime) {
this.effectiveTime = effectiveTime;
}
public String getStackTrace() {
return stackTrace;
}
public void setStackTrace(String stackTrace) {
this.stackTrace = stackTrace;
}
public String getExceptionClassName() {
return exceptionClassName;
}
public void setExceptionClassName(String exceptionClassName) {
this.exceptionClassName = exceptionClassName;
}
public String getThrowerClassName() {
return throwerClassName;
}
public void setThrowerClassName(String throwerClassName) {
this.throwerClassName = throwerClassName;
}
}
Now, on the client side when I'm trying to use the webservice method like so:
package adiib.server;
private void getStartupLogMessages() {
private static List<LogMessage> logMessages = new ArrayList<LogMessage>();
dsto.adiib.ws.client.harmoniser.AdiibHarmoniser_Service service = new dsto.adiib.ws.client.harmoniser.AdiibHarmoniser_Service();
dsto.adiib.ws.client.harmoniser.AdiibHarmoniser port = service.getAdiibHarmoniserPort();
List<dsto.adiib.ws.client.harmoniser.LogMessage> startupLogMessages = port.getStartupLogMessages();
for (adiib.ws.client.harmoniser.LogMessage logMessage : startupLogMessages) {
/*
* this fails becuase it's looking for adiib.ws.client.harmoniser.LogMessage
* not adiib.shared.LogMessage; adiib.ws.client.harmoniser.LogMessage is the
* generated type..
*/
logMessages.add((LogMessage) logMessage);
}
}
Any ideas? All I can think is creating a conversion method.. that seems wrong.
WulfgarPro
The classes generated by the tool are not the same as the original ones you have. So you have to use the tool generated ones in your client side to communicate with the web service. You cannot replace it with the ones you wrote for your server side.
For example, consider JAX-WS built client side DTOs. If you open up the source code, you will see that the auto-generated ones (using wsimport) contains annotations which may not be present (unless you manually wrote) in your server side classes. Therefore, as of my understanding, you have to go with the tool generated ones.
You might have to write methods to transform your DTOs to the tool generated one before web service is invoked. If your generated classes have the same set of properties (type and naming has not been altered by the tool when generating client DTOs), then probably you could use something like Apache Commons BeanUtils (see http://commons.apache.org/beanutils/) to aid in transformation. You can simply call BeanUtils.copyProperties() and pass in your source DTO (your own type) and the target DTO (WS generated) and get it transformed.
you right, generated class for the stub are images of the POJO classes.
They are generated to transfer data from remote server.
You have to use setter/getter and adapt data on your POJO.
Your method need to be wrapped in another method which belong to a service class. (call it MyClassServiceImpl)
and call the method in your application implementation.
Add the following to your LogMessage class (in the service):
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "LogMessage")
public class LogMessage implements Serializable
{ ... }
Rebuild the server.
In your client application, go to Web Service References and Right Click -> Refresh... the service.
The LogMessage class that you want to use will then appear in the Generated Sources folder of the client.