REST API return entire queried JSON - spring

I do maintain an small REST API (SpringBoot) offering services to obtain data about ticketing from a small sized retailer. i.e.: you can send a request in order to get some information from a certain ticket (which has unique ID); the JSON response consists of a selection of fields from the unique ticket (which is stored as an unique document in Mongo DB).
Let say the API receives a request, then it would execute a query to Mongo DB, and then apply a projection to parse the queried data into a data model class, which in turn is finally parsed to a response JSON like, i.e.:
{
"ticketData": {
"retailerId": "023",
"ticketId": "09834723469324",
"ticketDate": "2021-06-20"
},
"buyerData": {
"buyerId": "LN4382"
}
}
Well, I am now required to return the entire queried JSON (that is, a JSON containing the whole ticket information, that has a lot of fields). ¿Is there any way to achieve this without creating a data model class with tens or hundreds of properties to match the stored ticket JSON? Even if I specify the API response using YAML and then use a codegen tool, is a lot of tedious work, and whenever the ticket JSON format evolves, I would need to change my DAO and response.
I just would like to send the original Mongo stored JSON and hand it back to the API client. Is there any way to achieve that?

You can utilize Jackson ObjectMapper which Spring already uses to serialize and deserialize JSON.
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
#RestController
public class HelloWorldController {
private final ObjectMapper objectMapper;
public HelloWorldController(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#GetMapping("/jsonList")
public ResponseEntity<List<JsonNode>> getJsonList() {
List<String> data = List.of("{\"number\": 1}",
"{\"number\": 2}",
"{\"number\": 3}");
List<JsonNode> nodes = toJsonNodeList(data);
return ResponseEntity.ok(nodes);
}
private List<JsonNode> toJsonNodeList(List<String> strings) {
List<JsonNode> nodes = new ArrayList<>();
for (String s : strings) {
try {
JsonNode node = this.objectMapper.readTree(s);
nodes.add(node);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
return nodes;
}
}

Related

Spring MVC: Saving values added into a form to load later

I have a ParticipantsController.java which can add/delete/edit participant names.
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import questForTheBest.domain.Participants;
#Controller
public class ParticipantsController {
#InitBinder("participant") // Validator for participant model
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new ParticipantsValidator());
}
List<Participants> participantList = new ArrayList<>(); // create a list of participants
#RequestMapping("/participants") // Participants page
public String testing(Model model) {
model.addAttribute("participantList", participantList);
return "forms/ParticipantMaster";
}
#RequestMapping(value = "/participantDetail", method = RequestMethod.GET) // Shows participants
public String participantDetail(#ModelAttribute("participant") Participants participant, #RequestParam(value="participantId", required=false, defaultValue="-1") int participantId) {
if (participantId >= 1) { // Shows participants with an id greater than or equal to 1
Participants p2 = participantList.stream().filter(p -> (p.getId() == participantId)).findAny().get(); // gets participants
participant.setId(p2.getId()); // sets participants id
participant.setName(p2.getName()); // sets participants name
} else {
participant.setId(Participants.lastId); // otherwise create a new participant id
Participants.lastId++; // increment last id
}
return "forms/ParticipantDetail";
}
#RequestMapping(value = "/addParticipant", method = RequestMethod.POST) // Adding participants page
public String addParticipant(#Valid #ModelAttribute("participant") Participants participant,BindingResult result, Model model) {
if (result.hasErrors()) { // validation
return "forms/ParticipantDetail";
}
else {
participantList.removeIf(p -> (p.getId() == participant.getId()));
participantList.add(participant); // add participants
model.addAttribute("participantList", participantList);
return "forms/ParticipantMaster";
}
}
#RequestMapping(value = "/deleteParticipant", method = RequestMethod.GET) // Deleting participants
public String deleteParticipant(#RequestParam(value="participantId", required=false, defaultValue="-1") int participantId, Model model) {
participantList.removeIf(p -> (p.getId() == participantId)); // removes the participant with id
model.addAttribute("participantList", participantList);
return "forms/ParticipantMaster";
}
}
I wish to be able to: everytime a list of names are entered into the form I would like them to be saved when a button "Save current names" is pressed so that they can be later loaded again on another page by being able to be selected from a drop down box and then a "load" button is clicked.
The overall application is a leaderboard so I would like the same participants to be able to be saved for example so it can be loaded when a certain class in school will need to use the leaderboard.
Does this require the use of a database or is it possible without.
Thank you.
Ofcourse you will need the database for permanent storage of the leaderboard data on the solid state device (SSD storage) given your use case as memory(RAM) gets flushed/reset on machine reboot or crashes. You can have a layer of in-memory or a distributed cache as a layer above the database layer using an in-memory data structure like you have used a list in your implementation or redis respectively to make your application efficient for serving reads.
You got to have a Database Access Object (DAO) layer in your application for performing database related operations.
Please refer to this link to understand how DAO layer is implemented in spring boot
https://www.baeldung.com/jsf-spring-boot-controller-service-dao
Hope this helps!

Where does the filter for Ehcache 3 simple web page caching call the cache?

I am trying to cache a simple web page in Ehcache. Thanks to some help from another SO post I discovered that I need to implement my own filter based on Ehcache 2 code. When I look at the filter I don't understand it. Where does it ever call the cache to return a value? Here is my implementation (quite possibly wrong):
package com.sentiment360.pulse.cache;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.Element;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.Configuration;
import static org.ehcache.config.builders.CacheManagerBuilder.newCacheManager;
import org.ehcache.core.Ehcache;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.ehcache.xml.XmlConfiguration;
import javax.servlet.http.HttpServletRequest;
public class SimplePageCachingFilter implements CachingFilter {
public static final String DEFAULT_CACHE_NAME = "SimplePageCachingFilter";
private Logger LOG = Logger.getLogger(this.getClass().getName());
private String cacheName="basicCache";
protected String getCacheName() {
if (cacheName != null && cacheName.length() > 0) {
LOG.log(Level.INFO,"Using configured cacheName of {}.", cacheName);
return cacheName;
} else {
LOG.log(Level.INFO,"No cacheName configured. Using default of {}.", DEFAULT_CACHE_NAME);
return DEFAULT_CACHE_NAME;
}
}
protected CacheManager getCacheManager() {
return CacheManager.getInstance();
}
protected String calculateKey(HttpServletRequest httpRequest) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(httpRequest.getMethod()).append(httpRequest.getRequestURI()).append(httpRequest.getQueryString());
String key = stringBuffer.toString();
return key;
}
}
See in the super class.
But you do implements CachingFilter ?! Where is that interface? It does look like you were trying to "copy" the previous Ehcache's SimplePageCachingFilter, right? You would also need to port that abstract super class (and maybe read a little about javax.servlet.Filter, in case these aren't entirely clear...)
Now, you may also want to ping the dev team on the Ehcache Dev Google group about this. They should be able to provide pointers and then help with the implementation. Looks like a good idea for a future pull request! :)

Creating a unique Id and saving it in DB

I am new to Spring REST. I have to achieve the below mentioned requirement using Spring REST. I have to use JPA Repository for DB interaction
I have 2 tables, Application and App_Config. Application table has the following rows:
id(Primary Key), ApplicationId, Status, Source_System. App_config table has the following rows: ApplicationId (foreign key), HeaderText, FooterText. I need to use java UUID to generate a unique id for the application every time a new application sends an HTTP POST request. Based on the ApplicationId generated I need to save the data in App_Config table. There is a possibility that the same application appears twice. In that case I have to retrieve the already generated ApplicationId and load the Header and Footer from App_Config table.
Please advise how to achieve this via POST method. I need to send back only the generated ApplicationId to the user
Partial solution of your problem (the other part is not understood) regarding sending a rest with UUID and the rest api will be server+/generator/uuid
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
#RestController
#RequestMapping("/generator")
public class UuidGeneratorRestController {
#RequestMapping(value = "/uuid", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<UUID> getUUID() {
UUID generated = UUID.randomUUID();
return new ResponseEntity(generated, HttpStatus.OK);
}
}

Is it possible to connect to two different buckets of couchbase in spring boot

I am trying to connect to two different buckets in couchbase using spring boot. But in a single spring boot application the database config only takes a single bucket name.
Is it possible to connect to more than one couchbase bucket in spring-boot?
So it seems you want to use Spring Data Couchbase from within a Spring Boot application, and have (at least) two different repositories backed by two different Bucket?
You'll have to customize your Spring Data configuration programmatically (as opposed to letting Spring Boot do all the heavy lifting), but that's possible.
Spring Boot creates a CouchbaseConfigurer through which it creates default Cluster and Bucket (as tuned in the properties file).
If you have a CouchbaseRepository on your classpath, it'll also attempt to configure Spring Data by instantiating a SpringBootCouchbaseDataConfiguration class.
You can customize that by extending the SpringBootCouchbaseDataConfiguration above in your project, marking it as #Configuration
Once you're ready to customize the Spring Data configuration programmatically, what you need is to create a second Bucket bean, a second CouchbaseTemplate that uses that bucket, and then instruct Spring Data Couchbase on which template to use with which Repository.
To that end, there is a configureRepositoryOperationsMapping(...) method. You can use the parameter of this method as a builder to:
link a specific Repository interface to a CouchbaseTemplate: map
say that any repo with a specific entity type should use a given template: mapEntity
even redefine the default template to use (initially the one created by Spring Boot): setDefault.
This second part is explained in the Spring Data Couchbase documentation.
Probably what you are trying to say is that Spring boot provides pre-defined properties that you can modify, such as: couchbase.cluster.bucket that takes single value and you want to connect to two or more buckets.
In case you will not find a better solution, I can point you to a slightly different approach, and that is to setup your own couchbase connection manager that you can inject anywhere you need.
Here is the example of such #Service that will provider you with two connections to different buckets.
You can modify to suite your needs, it is very small.
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
#Service
public class CouchbaseConnectionManager {
private static final int TIMEOUT = 100000;
#Value("#{configProp['couchbase.nodes']}")
private List<String> nodes = new ArrayList<String>();
#Value("#{configProp['couchbase.binary.bucketname']}")
private String binaryBucketName;
#Value("#{configProp['couchbase.nonbinary.bucketname']}")
private String nonbinaryBucketName;
#Value("#{configProp['couchbase.password']}")
private String password;
private Bucket binaryBucket;
private Bucket nonbinaryBucket;
private Cluster cluster;
private static final Logger log = Logger.getLogger(CouchbaseConnectionManager.class);
#PostConstruct
public void createSession() {
if (nodes != null && nodes.size() != 0) {
try {
CouchbaseEnvironment env = DefaultCouchbaseEnvironment.builder().connectTimeout(TIMEOUT).build();
cluster = CouchbaseCluster.create(env, nodes);
binaryBucket = cluster.openBucket(binaryBucketName, password);
nonbinaryBucket = cluster.openBucket(nonbinaryBucketName, password);
log.info(GOT_A_CONNECTION_TO_COUCHBASE_BUCKETS + binaryBucket + " " + nonbinaryBucket);
} catch (Exception e) {
log.warn(UNABLE_TO_GET_CONNECTION_TO_COUCHBASE_BUCKETS);
}
} else {
log.warn(COUCH_NOT_CONFIGURED);
}
}
#PreDestroy
public void preDestroy() {
if (cluster != null) {
cluster.disconnect();
log.info(SUCCESSFULLY_DISCONNECTED_FROM_COUCHBASE);
}
}
public Bucket getBinaryBucket() {
return binaryBucket;
}
public Bucket getNonbinaryBucket() {
return nonbinaryBucket;
}
private static final String SUCCESSFULLY_DISCONNECTED_FROM_COUCHBASE = "Successfully disconnected from couchbase";
private static final String GOT_A_CONNECTION_TO_COUCHBASE_BUCKETS = "Got a connection to couchbase buckets: ";
private static final String COUCH_NOT_CONFIGURED = "COUCH not configured!!";
private static final String UNABLE_TO_GET_CONNECTION_TO_COUCHBASE_BUCKETS = "Unable to get connection to couchbase buckets";
}
I followed Simon's approach and extended the org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration for the #Configuration instead of SpringBootCouchbaseDataConfiguration.
Also, a point worth mentioning is that for some reason having separate Repository packages and having its own #Configuration doesn't really work. I struggled a great deal to try and make it work and eventually settled on having all the Repositories in a single package and ended up having something like the below to map the Entities and Templates.
baseMapping.mapEntity(Prime.class, noSQLSearchDBTemplate())
.mapEntity(PrimeDetailsMaster.class, noSQLSearchDBTemplate())
.mapEntity(HostDetailsMaster.class, noSQLSearchDBTemplate())
.mapEntity(Events.class, eventsTemplate())
.mapEntity(EventRulesMaster.class, eventsTemplate());

deserializing multiple types with gson

I receive the following JSON response from my http web service
{"status":100,"data":[{"name":"kitchen chair","price":25.99,"description":null}]}
Now I want to be able to deserialize this. I stumbled upon Gson from Google, which at first worked well for some small testcases but I'm having some trouble getting the above string deserialized.
I know the data field only has one item in it, but it could hold more than one which is why all responses have the data field as an array.
I was reading the Gson User Guide and ideally I would like to have a Response object which has two attributes: status and data, but the data field would have to be a List of Map objects which presumably is making it hard for Gson.
I then looked at this which is an example closer to my problem but I still can't quite figure it out. In the example the whole response is an array, whereas my JSON string has one string element and then an array.
How would I best go about deserializing this?
It's not clear what exactly was already attempted that appeared to be "hard for Gson".
The latest release of Gson handles this simply. Following is an example.
input.json
{
"status":100,
"data":
[
{
"name":"kitchen chair",
"price":25.99,
"description":null
}
]
}
Response.java
import java.util.List;
import java.util.Map;
class Response
{
int status;
List<Map> data;
}
GsonFoo.java
import java.io.FileReader;
import com.google.gson.Gson;
public class GsonFoo
{
public static void main(String[] args) throws Exception
{
Response response = new Gson().fromJson(new FileReader("input.json"), Response.class);
System.out.println(new Gson().toJson(response));
}
}
Output:
{"status":100,"data":[{"name":"kitchen chair","price":25.99}]}

Resources