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
Related
I have a customer's database that has a collection, in which the document fields can vary between each other. There are some constant fields I can rely on, but as for the rest - I have no way of narrowing the field list as the customer wants the solution to be dynamic.
My question is - can I somehow implement a generic mapping that would return, let's say, a map of document's fields using Spring Data?
edit:
Thanks for the tips. I've tried getting the generic Object (hoping I'd be able to convert it into a map) using the entity:
#Document(collection = "Data")
public class DataEntity {
#Id
private String id;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
but fetching the object by the ID using MongoRepository produces an object with data field set to null.
I'm using SpringBoot 1.3.1.RELEASE with spring-boot-starter-data-mongodb 1.3.1.RELEASE.
You can use a Map for dynamic properties like below. Is this what you are looking for?
#Document(collection = "computers")
public class Computer {
#Id
private String id;
#Field("name")
private String name;
//Other constant fields
#Field("properties")
private Map<String, Object> properties;
}
I didn't have this problem before, with other POJOs, I'm not sure what's different this time, but I can't get this working and I could not find an exact solution for this.
I have this POJO called Component (with some Hibernate annotations):
#Entity
#Table(name="component", uniqueConstraints={#UniqueConstraint(
columnNames = {"name", "component_type"})})
public class Component {
#Column(name="id")
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="name")
private String name;
#Column(name="component_type")
private String componentType;
#Column(name="serial_number")
private int serialNumber;
#Column(name="active_since")
private String activeSince;
#Embedded
private ComponentWearoutModel wearout;
public Component() {
}
public Component(String name, String componentType, int serialNumber, String activeSince,
ComponentWearoutModel wearout) {
this.name = name;
this.componentType = componentType;
this.serialNumber = serialNumber;
this.activeSince = activeSince;
this.wearout = wearout;
}
public ComponentWearoutModel getModel() {
return wearout;
}
public void setModel(ComponentWearoutModel wearout) {
this.wearout = wearout;
}
//more getters and setters
}
ComponentWearoutModel:
#Embeddable
public class ComponentWearoutModel {
private String componentType; //dont mind the stupid duplicate attribute
private Integer componentLifeExpectancy;
private Float componentWearOutLevel;
private Float actionThreshold;
public ComponentWearoutModel() {
}
public ComponentWearoutModel(String componentType, int componentLifeExpectancy, float componentWearOutLevel,
float actionThreshold) {
this.componentType = componentType;
this.componentLifeExpectancy = componentLifeExpectancy;
this.componentWearOutLevel = componentWearOutLevel;
this.actionThreshold = actionThreshold;
}
//getters and setters
}
The sample payload I use:
{
"name": "component name",
"componentType": "airfilter2",
"serialNumber": 573224,
"activeSince": "2016-04-10 17:38:41",
"wearout":
{
"componentType": "airfilter",
"componentLifeExpectancy": 1000,
"componentWearOutLevel": 0.24,
"actionThreshold": 0.2
}
}
And finally the resource class:
#Path("myresource")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public class MyResource {
DatabaseManager dm = DatabaseManager.getInstance();
#PUT
#Path("Component")
public Response storeComponent(Component component){
System.out.println("reached");
System.out.println(component.getComponentType()); //okay
System.out.println(component.getModel().getComponentType()); //nullpointerexception
ComponentWearoutModel model = new ComponentWearoutModel("type", 1000, 1f, 0.2f);
component.setModel(model); //this way it's saved in the db just fine
dm.save(component);
return Response.status(Status.OK).entity(component).build();
}
}
Without the prints, only the fields which are not part of the ComponentWearoutModel class are stored in the database table, the other columns are null. So when I try to print one of them, I get an exception, I just dont understand why. If I create a ComponentWearoutModel in the resource method and add it to the component, everything is fine in the database.
UPDATE:
so my mistake was that I named the ComponentWearoutModel attribute as "wearout" in the Component.class, but the autogenerated getters and setter were called getModel/setModel and moxy could not parse my payload because of this. Solution: change the attribute name to "model" in Component class and in payload too.
Please ensure that the attribute names you are using in the POJO are same as what are being sent in the json string.
Since there are no jackson etc annotations being used in your POJO to tell it the corresponding json mapping, the underlying code will directly use the names given in json string. If you are using the string "model", the convertor code will look for a "setModel" method in your POJO.
In the above example, either call everything "model", or "wearable".
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.
I'm using spring-boot to provide a REST interface persisted with MongoDB. I'm using the 'standard' dependencies to power it, including spring-boot-starter-data-mongodb and spring-boot-starter-web.
However, in some of my classes I have fields that I annotate #Transient so that MongoDB does not persist that information. However, this information I DO want sent out in my rest services. Unfortunately, both MongoDB and the rest controller seem to share that annotation. So when my front-end receives the JSON object, those fields are not instantiated (but still declared). Removing the annotation allows the fields to come through in the JSON object.
How I do configure what is transient for MongoDB and REST separately?
Here is my class
package com.clashalytics.domain.building;
import com.clashalytics.domain.building.constants.BuildingConstants;
import com.clashalytics.domain.building.constants.BuildingType;
import com.google.common.base.Objects;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import java.util.*;
public class Building {
#Id
private int id;
private BuildingType buildingType;
private int level;
private Location location;
// TODO http://stackoverflow.com/questions/30970717/specify-field-is-transient-for-mongodb-but-not-for-restcontroller
#Transient
private int hp;
#Transient
private BuildingDefense defenses;
private static Map<Building,Building> buildings = new HashMap<>();
public Building(){}
public Building(BuildingType buildingType, int level){
this.buildingType = buildingType;
this.level = level;
if(BuildingConstants.hpMap.containsKey(buildingType))
this.hp = BuildingConstants.hpMap.get(buildingType).get(level - 1);
this.defenses = BuildingDefense.get(buildingType, level);
}
public static Building get(BuildingType townHall, int level) {
Building newCandidate = new Building(townHall,level);
if (buildings.containsKey(newCandidate)){
return buildings.get(newCandidate);
}
buildings.put(newCandidate,newCandidate);
return newCandidate;
}
public int getId() {
return id;
}
public String getName(){
return buildingType.getName();
}
public BuildingType getBuildingType() {
return buildingType;
}
public int getHp() {
return hp;
}
public int getLevel() {
return level;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public BuildingDefense getDefenses() {
return defenses;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Building building = (Building) o;
return Objects.equal(id, building.id) &&
Objects.equal(hp, building.hp) &&
Objects.equal(level, building.level) &&
Objects.equal(buildingType, building.buildingType) &&
Objects.equal(defenses, building.defenses) &&
Objects.equal(location, building.location);
}
#Override
public int hashCode() {
return Objects.hashCode(id, buildingType, hp, level, defenses, location);
}
}
As is, hp and defenses show up as 0 and null respectively. If I remove the #Transient tag it comes through.
As long as you use org.springframework.data.annotation.Transient it should work as expected. Jackson knows nothing about spring-data and it ignores it's annotations.
Sample code, that works:
interface PersonRepository extends CrudRepository<Person, String> {}
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
#Document
class Person {
#Id
private String id;
private String name;
#Transient
private Integer age;
// setters & getters & toString()
}
#RestController
#RequestMapping("/person")
class PersonController {
private static final Logger LOG = LoggerFactory.getLogger(PersonController.class);
private final PersonRepository personRepository;
#Autowired
PersonController(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#RequestMapping(method = RequestMethod.POST)
public void post(#RequestBody Person person) {
// logging to show that json deserialization works
LOG.info("Saving person: {}", person);
personRepository.save(person);
}
#RequestMapping(method = RequestMethod.GET)
public Iterable<Person> list() {
Iterable<Person> list = personRepository.findAll();
// setting age to show that json serialization works
list.forEach(foobar -> foobar.setAge(18));
return list;
}
}
Executing POST http://localhost:8080/person:
{
"name":"John Doe",
"age": 40
}
Log output Saving person: Person{age=40, id='null', name='John Doe'}
Entry in person collection:
{ "_id" : ObjectId("55886dae5ca42c52f22a9af3"), "_class" : "demo.Person", "name" : "John Doe" } - age is not persisted
Executing GET http://localhost:8080/person:
Result: [{"id":"55886dae5ca42c52f22a9af3","name":"John Doe","age":18}]
I solved by using #JsonSerialize. Optionally you can also opt for #JsonDeserialize if you want this to be deserailized as well.
#Entity
public class Article {
#Column(name = "title")
private String title;
#Transient
#JsonSerialize
#JsonDeserialize
private Boolean testing;
}
// No annotations needed here
public Boolean getTesting() {
return testing;
}
public void setTesting(Boolean testing) {
this.testing = testing;
}
The problem for you seems to be that both mongo and jackson are behaving as expected. Mongo does not persist the data and jackson ignores the property since it is marked as transient. I managed to get this working by 'tricking' jackson to ignore the transient field and then annotating the getter method with #JsonProperty. Here is my sample bean.
#Entity
public class User {
#Id
private Integer id;
#Column
private String username;
#JsonIgnore
#Transient
private String password;
#JsonProperty("password")
public String getPassword() {
return // your logic here;
}
}
This is more of a work around than a proper solution so I am not sure if this will introduce any side effects for you.
carlos-bribiescas,
what version are you using for it. It could be version issue. Because this transient annotation is meant only for not persisting to the mongo db. Please try to change the version.Probably similar to Maciej one (1.2.4 release)
There was issue with json parsing for spring data project in one of the version.
http://www.widecodes.com/CyVjgkqPXX/fields-with-jsonproperty-are-ignored-on-deserialization-in-spring-boot-spring-data-rest.html
Since you are not exposing your MongoRepositories as restful endpoint with Spring Data REST it makes more sense to have your Resources/endpoint responses decoupled from your domain model, that way your domain model could evolve without impacting your rest clients/consumers. For the Resource you could consider leveraging what Spring HATEOAS has to offer.
I solved this question by implementing custom JacksonAnnotationIntrospector:
#Bean
#Primary
ObjectMapper objectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
AnnotationIntrospector annotationIntrospector = new JacksonAnnotationIntrospector() {
#Override
protected boolean _isIgnorable(Annotated a) {
boolean ignorable = super._isIgnorable(a);
if (ignorable) {
Transient aTransient = a.getAnnotation(Transient.class);
JsonIgnore jsonIgnore = a.getAnnotation(JsonIgnore.class);
return aTransient == null || jsonIgnore != null && jsonIgnore.value();
}
return false;
}
};
builder.annotationIntrospector(annotationIntrospector);
return builder.build();
}
This code makes invisible org.springframework.data.annotation.Transient annotation for Jackson but it works for mongodb.
You can use the annotation org.bson.codecs.pojo.annotations.BsonIgnore instead of #transient at the fields, that MongoDB shall not persist.
#BsonIgnore
private BuildingDefense defenses;
It also works on getters.
I tried the Spring Guide Accessing Data with MongoDB. What I can't figure out is how do I configure my code to not use the default server address and not use the default database. I have seen many ways to do it with XML but I am trying to stay with fully XML-less configurations.
Does anyone have an example that sets the server and database without XML and can be easily integrated into the sample they show in the Spring Guide?
Note: I did find how to set the collection (search for the phrase "Which collection will my documents be saved into " on this page.
Thank you!
p.s. same story with the Spring Guide for JPA -- how do you configure the db properties -- but that is another post :)
It would be something like this for a basic configuration :
#Configuration
#EnableMongoRepositories
public class MongoConfiguration extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "dataBaseName";
}
#Override
public Mongo mongo() throws Exception {
return new MongoClient("127.0.0.1", 27017);
}
#Override
protected String getMappingBasePackage() {
return "foo.bar.domain";
}
}
Example for a document :
#Document
public class Person {
#Id
private String id;
private String name;
public Person(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Example for a repository :
#Repository
public class PersonRepository {
#Autowired
MongoTemplate mongoTemplate;
public long countAllPersons() {
return mongoTemplate.count(null, Person.class);
}
}