Struts 2 json annotation - json

Ive been wanting to create a struts 2 with json return type using the annotation configuration. Ive successfully created this using the xml-type configuration like this snippet:
<action name="FetchJSON" class="com.stikiflem.Json" method="getJSON">
<result type="json"/>
</action>
I have posted a working demo of using an xml-type config here
http://stikiflem.wordpress.com/2008/08/27/struts-2-json-sample/
But how do I convert this to annotation? Here is my sample class:
public class JsonAction extends ActionSupport{
private List sampleList;
public String execute() {
sampleList = new ArrayList();
sampleList.add("stikiflem sample 1");
sampleList.add("stikiflem sample 2");
sampleList.add("stikiflem sample 3");
sampleList.add("stikiflem sample 4");
System.out.println("----------------------------------------------");
System.out.println("----------------------------------------------");
System.out.println("-sample111List:" + sampleList.toString());
System.out.println("----------------------------------------------");
System.out.println("----------------------------------------------");
return SUCCESS;
}
#Action(value="FetchJSON", results = {
#Result(name="success", type="json")
})
public String getJSON(){
System.out.println("get jason ko");
return execute();
}
public List getSampleList() {
return sampleList;
}
public void setSampleList(List sampleList) {
this.sampleList = sampleList;
}
}
Tried calling it by "json.action", it triggers the execute() method of course but cannot return a json type. Calling it by "FetchJSON" doesnt do anything. This question sounds stupid but there are just a small amount of tutorials and example of a detailed annotation in the net. Ive read a Manning Struts 2 in action book but it just barely scratch the surface, just the typical hello world and sucess,input redirection.
Ive searched the net high and low and so far, i havent seen any. I know there are a lot of programmers searching for this too.Hope someone can enlighten me about this one. Ive been banging my head on this for days already. :(

A similar question was asked here:
Struts2 JSON Plugin With Annotations
I got your action working by annotating it as follows:
#ParentPackage("json-default")
#Result(name="success", type="json")
public class JsonAction extends ActionSupport {

Get the JAR Dependencies
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>2.3.20</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-json-plugin</artifactId>
<version>2.3.20</version>
</dependency>
Convention Plugin
The Convention Plugin is bundled with Struts since 2.1 and replaces the Codebehind Plugin and Zero Config plugins. It provides the following features :
Action location by package naming conventions
Result (JSP, FreeMarker, etc) location by naming conventions
Class name to URL naming convention
Package name to namespace convention
Action name overrides using annotations
Namespace overrides using annotations
XWork package overrides using annotations
Set Parent Package
Using annotation set the package as json-default to support the JSON.
#ParentPackage("json-default")
Set Result Type
#Result(name="success", type="json")
Define filter in web.xml
Define the struts 2 filter in web.xml and pass the action class by defining actionPackages.
Action Class
In this class data converted into JSON format.
#Result(name = "success", type = "json")
#ParentPackage("json-default")
public class StrutsJsonAnnotationAction extends ActionSupport {
private static final long serialVersionUID = 3516335522937177571L;
private String name = "Narendra Modi";
private String designation = "Prime Minister of India";
private String dob = "17 September 1950";
private String[] education = {"MA", "BA"};
private List<String> favBooks = new ArrayList<String>();
private Map<String, String> assumedOffice = new HashMap<String, String>();
public StrutsJsonAnnotationAction() {
favBooks.add("Ramayan");
favBooks.add("Geeta");
assumedOffice.put("President", "Pranab Mukherjee");
assumedOffice.put("Preceded by", "Manmohan Singh");
}
#org.apache.struts2.convention.annotation.Action("/india")
#Override
public String execute() {
return SUCCESS;
}
Source:
http://www.websparrow.org/struts/struts2-and-json-integration-using-annotation-example

Related

LazyInitializationException when returning JSON in REST Webservice in Quarkus

I'm trying to build a simple application with Quarkus. Currently, I have two entity classes, which are related one-to-many:
#Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birthdate;
#OneToMany(mappedBy = "person")
public List<Address> addresses;
public static Person findByNameFirst(String name) {
return find("name", name).firstResult();
}
}
#Entity
public class Address extends PanacheEntity {
public String street;
...etc...
#ManyToOne
public Person person;
}
These are used by a simple REST webservice, which should store a Person to the database, select it again an return it:
#GET
#Path("storePerson")
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public Person storePerson(
#QueryParam("name")String name,
#QueryParam("birthdate")String birthdate)
{
LocalDate birth = LocalDate.parse(birthdate, DateTimeFormatter.BASIC_ISO_DATE);
Person person = new Person(name, birth);
person.persistAndFlush();
Person p2 = Person.findByNameFirst(name);
return p2;
}
When calling the webservice the first time, the result is a JSON object with the stored data, which is as expected. When called again, an internal server error is thrown:
org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [Person.addresses] - no session and settings disallow loading outside the Session
As I understand, the error is thrown because the transaction only lasts until the storePerson method ends, but the conversion to JSON is happening outside of the method.
How can I prevent this error? I have read about the hibernate parameter "enable_lazy_load_no_trans" but it seems it is not supported in Quakus' application.properties.
The idea is to use a mapper framework such as MapStruct.
We don't recommend to directly expose your entities for 2 reasons:
the issue you have,
API management in the long run: you might have to change your model and not your API or the opposite.
There is an example here: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-quarkus .
The Quarkus version used is a bit old but AFAICS it should still work with latest Quarkus.
You can make the error go away by using Hibernate.initialize(person.addresses), then the collection gets initialized before the transaction ends.

Can not convert JSON to domain object with spring restTemplate

Actually I try to invoke a get request with the restTemplate in Spring. Debbuging my application clearly shows that the JSON is downloaded but the automatic mapping does not work. My List of domain object includes only 0 values and null values.
When I invoke the get request from the browser, I get the following response as JSON (I copied here the first two record out of the 3 192):
[{"OrderId":77862,"DateAdded":"2016-04-30T02:25:40.263","OrderItemCorpusId":"HUBW","OrderItemCorpusOriginalId":null,"OrderItemCurrency":"HUF","OrderItemExchangeRate":1.00000,"OrderItemOriginalLocation":"HU","OrderItemBuyLocation":"HU","OrderItemPrice":1337.80314,"OrderItemDiscountId":0,"OrderItemDiscountValue":"","DocumentId":25140,"Title":"Romana Gold 10. kötet","PublisherOriginalName":"Harlequin Kiadó","ISBN":"9789634073369"},{"OrderId":77864,"DateAdded":"2016-04-30T15:49:22.61","OrderItemCorpusId":"HUBW","OrderItemCorpusOriginalId":null,"OrderItemCurrency":"HUF","OrderItemExchangeRate":1.00000,"OrderItemOriginalLocation":"HU","OrderItemBuyLocation":"HU","OrderItemPrice":2748.03149,"OrderItemDiscountId":0,"OrderItemDiscountValue":"","DocumentId":25252,"Title":"Az eltűnt lány","PublisherOriginalName":"Harlequin Kiadó","ISBN":"9789634072423"}]
My POJO domain object which should keep the converted data from JSON:
#JsonIgnoreProperties(ignoreUnknown = true)
public class BandWTransaction {
private long OrderId;
private Date DateAdded;
private String OrderItemCurrency;
private double OrderItemExchangeRate;
private String OrderItemBuyLocation;
private double OrderItemPrice;
private String OrderItemDiscountValue;
private long DocumentId;
private String Title;
private String PublisherOriginalName;
private String ISBN;
//getters and setters
Finally the code snippet I use for the rest get request:
String startDate = new SimpleDateFormat("yyyy-MM-dd").format(start.getTime());
String endDate = new SimpleDateFormat("yyyy-MM-dd").format(end.getTime());
UriComponents uri = UriComponentsBuilder.newInstance().scheme("http").host("www.bookandwalk.hu")
.path("/api/AdminTransactionList").queryParam("password", "XXX")
.queryParam("begindate", startDate).queryParam("enddate", endDate).queryParam("corpusid", "HUBW")
.build().encode();
LOG.log(Level.INFO, "{0} were called as a rest call", uri.toString());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.set("User-Agent", "Anything");
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<List<BandWTransaction>> transResponse = restTemplate.exchange(uri.toString(), HttpMethod.GET,
entity, new ParameterizedTypeReference<List<BandWTransaction>>() {
});
List<BandWTransaction> transactions = transResponse.getBody();
When I debug the app I realized that the transactions list includes objects with full of null and 0 values. More precisely, there is no and objcet within the list having other values as 0 and null in the properties.
I have also checked that spring boot automatically registered in the restTemplate.messageConverters ArrayList 9 HttpMessageConverter. The 7th element of this ArrayList is the org.springframework.http.converter.json.MappingJackson2HttpMessageConverter which supports the application/json and application/+json media types.
Any idea is appreciated to solve this problem as I am newbie in spring and in JSON mapping in general.
It seems you have a naming convention issue due to your field variables starts with a uppercase. When Jackson finds a pair getTitle/setTitleasumes that the name of this variable is title (starting with lowercase). Of course, if you change the capitalization of your variables, json properties and java variables has different names, so mapping still fails. The best solution is change your names to meet Java conventions, and use Jackson annotations to define your mappings.
#JsonProperty(value="OrderId")
private int orderId;
#JsonProperty(value="DateAdded")
private Date dateAdded;
Hope it helps.
I can suggest you to write a test and check how fasterxml ObjectMapper read a json and unmarshall json to your object:
ObjectMapper objectMapper = new ObjectMapper();
String somestring = objectMapper.readValue("somestring", String.class);
just replace String with your class and "somestring" with your json. So you check if there is problem with it.
And try to use #JsonPropery cause all this capital letters fields start with looks messy:
#JsonIgnoreProperties(ignoreUnknown = true)
public class BandWTransaction {
#JsonProperty("OrderId")
private long OrderId;
[...]
With this stuff I read json correct. You can come in from other side remove ignoring unknown properties:
#JsonIgnoreProperties(ignoreUnknown = true) //remove it and run test
public class BandWTransaction {
and you get :
(11 known properties: "dateAdded", "orderItemExchangeRate",
"documentId", "orderItemPrice", "orderId", "orderItemBuyLocation",
"orderItemDiscountValue", "orderItemCurrency", "isbn", "title",
"publisherOriginalName"])
So problem in variables naming and you can fix it with #JsonProperty

REST: how to serialize a java object to JSON in a "shallow" way?

Suppose I have the following JPA entities:
#Entity
public class Inner {
#Id private Long id;
private String name;
// getters/setters
}
#Entity
public class Outer {
#Id private Long id;
private String name;
#ManyToOne private Inner inner;
// getters/setters
}
Both Spring and java EE have REST implementations with default serializers which will marshall the entities to/from JSON without further coding. But when converting Outer to JSON, both Spring and EE nest a full copy of Inner within it:
// Outer
{
"id": "1234",
"name": "MyOuterName",
"inner": {
"id": "4321",
"name": "MyInnerName"
}
}
This is correct behavior but problematic for my web services, since the object graphs can get deep/complex and can contain circular references. Is there any way to configure the supplied marshaller to marshall the POJOs/entities in a "shallow" way instead without having to create a custom JSON serializer for each one? One custom serializer that works on all entities would be fine. I'd ideally like something like this:
// Outer
{
"id": "1234",
"name": "MyOuterName",
"innerId": "4321"
}
I'd also like it to "unmarshall" the JSON back into the equivalent java object. Bonus kudos if the solution works with both Spring and java EE. Thanks!
After many problems I give reason to Cássio Mazzochi Molin saying that "the use of entities persistence in your REST API can not be a good idea"
I would do that the business layer transform persistence entities to DTO.
You can do this very easily with libraries like mapstruct
If you still want to continue with this bad practice you can use jackson and customize your jackson mapper
To unscramble complex object graphs using jaxb #XmlID and #XmlIDREF is made for.
public class JSONTestCase {
#XmlRootElement
public static final class Entity {
private String id;
private String someInfo;
private DetailEntity detail;
#XmlIDREF
private DetailEntity detailAgain;
public Entity(String id, String someInfo, DetailEntity detail) {
this.id = id;
this.someInfo = someInfo;
this.detail = detail;
this.detailAgain = detail;
}
// default constructor, getters, setters
}
public static final class DetailEntity {
#XmlID
private String id;
private String someDetailInfo;
// constructors, getters, setters
}
#Test
public void testMarshalling() throws JAXBException {
Entity e = new Entity( "42", "info", new DetailEntity("47","detailInfo") );
JAXBContext context = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[]{Entity.class}, null);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
m.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
m.marshal(e, System.out);
}
}
This will result in the following json-fragment
{
"detailAgain" : "47",
"detail" : {
"id" : "47",
"someDetailInfo" : "detailInfo"
},
"id" : "42",
"someInfo" : "info"
}
Unmarshalling of this json will ensure that detail and detailAgain are the same instances.
The two annotations are part of jaxb, so it will work in Spring as well as in java EE. Marshalling to json is not part of the standard, so i use moxy in the example.
Update
Explicitly using moxy is not neccessary in a JAX-RS Resource. The following snipped perfectly runs on a java-EE-7 container (glassfish 4.1.1) and results in the above json-fragment:
#Stateless
#Path("/entities")
public class EntityResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Entity getEntity() {
return new Entity( "42", "info", new DetailEntity("47","detailInfo") );
}
}
I had the same problem and ended up using jackson annotations on my Entities to control the serialization:
What you need is #JsonIdentityReference(alwaysAsId=true) to instruct the bean serializer that this reference should be only an ID. You can see an example on my repo:
https://github.com/sashokbg/company-rest-service/blob/master/src/main/java/bg/alexander/model/Order.java
#OneToMany(mappedBy="order", fetch=FetchType.EAGER)
#JsonIdentityReference(alwaysAsId=true) // otherwise first ref as POJO, others as id
private Set<OrderDetail> orderDetails;
If you want a full control of how your entities are represented as JSON, you can use JsonView to define which field is serialized related to your view.
#JsonView(Views.Public.class)
public int id;
#JsonView(Views.Public.class)
public String itemName;
#JsonView(Views.Internal.class)
public String ownerName;
http://www.baeldung.com/jackson-json-view-annotation
Cheers !
for this problem There are two solutions.
1-using jackson json view
2- Createing two mapping classe for innner entity. one of them includes custom fields and another one includes all fields ...
i think jackson json view is better solution ...
Go through the FLEXJSON library to smartly include/exclude nested class hierarchy while serializing Java objects.
Examples for flexjson.JSONSerializer presented here
You can detach the JPA entity before serialization, if you use lazyloading it's avoid to load sub objects.
Another way, but is depend of the JSON serializer API, you can use "transient" or specifics annotation.
Why does JPA have a #Transient annotation?
A bad way is to use tool like dozer to copy JPA object in another class with only the properties need for json (but it works... little overhead of memory, CPU and time...)
#Entity
public class Outer {
#Id private Long id;
private String name;
#ManyToOne private Inner inner;
//load manually inner.id
private final Long innerId;
// getters/setters
}

How to make Enunciate show my data type as structured JSON (instead of as "custom")?

I have this simple service which echoes an ID parameter wrapped in a JSON object:
#Path("job")
public class JobResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("{id}")
public Job readById(#PathParam("id") long id) {
Job j = new Job();
j.id = id;
return j;
}
}
The return value's Job class is declared as:
public class Job {
public long id;
}
The documentation generated with Enunciate shows the service's data type as "custom". Is it possible to have Enunciate spit out a more detailed explanation of the return type, for example a JSON representation?
If you change the return type to javax.ws.rs.core.Response the documentation should then show the data type as JSON.
You would need to slightly modify your method as:
...
return Response.status(Status.OK).entity(j).build();
Add annotation to the Job class
#javax.xml.bind.annotation.XmlRootElement
Without this annotation the enunciate will display the DTO as "custom" or "file"
I got exactly the same problem with a simple REST Jersey webservice.
Here are the annotations of My returned object (no more):
#XmlRootElement(name = "OReponseInitialiser")
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlType (name="OReponseInitialiser")
public class OReponseInitialiser
And the webservice declaration:
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Path ("initialiser")
public OReponseInitialiser initialiser(#Context HttpServletRequest pRequete, ...) throws OException
I build the documentation with the ant task
<enunciate
basedir="${projet.repertoire}/src"
buildDir="${enunciate.working.dir}"
javacSourceVersion="1.8"
javacTargetVersion="1.8"
configFile="${projet.repertoire}/build_enunciate.xml"
>
<include name="**/*.java"/>
<classpath refid="compile.classpath"/>
<export artifactId="docs" destination="${docs.dir}"/>
</enunciate>

Filtering entity fields dynamically in Spring Data rest json Response

Hi I have a requirement to dynamically ignore entity fields in spring data rest response [I know they can be done in a static way by using #JsonIgnore annotation] ideally based on a spring security Role .The role part is still manageable but how to dynamically ignore fields in the json response is a challenge.
After some analysis and the docs I think jackson is the way to go as spring data rest does provide jackson customization via jackson modules and mixins http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.custom-jackson-deserialization .
So I think in jackson api it could be done via #jsonFilter and then suppling the same when the ObjectMapper write the object [more details here http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html] .
But I am not sure how this could be wired up with Spring data rest (basically the part where I acan inject the filterprovider into spring data rest objectmapper).Let me know if anyone has tried this or someone from the Spring data team has insights .
Will post an answer myself If I am able to achieve the same.
UPDATE
So I figured out that the way to implement custom filtering is through the jackson BeanSerializerModifier .Got great help from #cowtowncoder on twitter .Also helpful reference or holy grails for filtering with jackson http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html
So yes finally I was able to solve this .The trick here is to use a custom BeanSerializerModifier and register it via a Custom Module [which is the custom hook available to customize spring data rest jackson serialization],something like
setSerializerModifier( new CustomSerializerModifier()).build()));
now you can customize our BeanSerializerModifier by overriding the method changeProperties to apply your custom filter ,which basically includes and excludes BeanPropertyWriter based on your logic .sample below
List<BeanPropertyWriter> included = Lists.newArrayList();
for (BeanPropertyWriter property : beanProperties)
if (!filter.contains(property.getName()))
included.add(property);
this way you can include any logic per class or otherwise and filter properties form response in a custom manner.Hope It Helps
Also have updated my code on github do look at https://github.com/gauravbrills/SpringPlayground
This example shows how to implement a dynamic JSON transformation (filtering) in a Spring Boot REST controller. It is using AOP controller advice to change controller method output in runtime. Code on github: https://github.com/andreygrigoriev/jsonfilter
AOP Advice
#ControllerAdvice
#SuppressWarnings("unused")
public class FilterAdvice implements ResponseBodyAdvice<Object> {
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
String fields = ((ServletServerHttpRequest) request).getServletRequest().getParameter("fields");
return new FilterMappingJacksonValue<>(body, StringUtils.isEmpty(fields) ? new String[] {} : fields.split(","));
}
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
}
FilterMappingJacksonValue
public class FilterMappingJacksonValue<T> extends MappingJacksonValue {
public FilterMappingJacksonValue(final T value, final String... filters) {
super(value);
setFilters(new SimpleFilterProvider().addFilter("dynamicFilter",
filters.length > 0 ? SimpleBeanPropertyFilter.filterOutAllExcept(filters) : SimpleBeanPropertyFilter.serializeAll()));
}
}
Simple DTO
#Data
#AllArgsConstructor
#JsonFilter("dynamicFilter")
public class Book {
String name;
String author;
}
BookController
#RestController
#SuppressWarnings("unused")
public class BookController {
#GetMapping("/books")
public List<Book> books() {
List<Book> books = new ArrayList<>();
books.add(new Book("Don Quixote", "Miguel de Cervantes"));
books.add(new Book("One Hundred Years of Solitude", "Gabriel Garcia Marquez"));
return books;
}
}