I build one API which is paginated, its output looks like this:
{
"content": [
{JSON1},
{JSON2},
...
{JSON20}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"offset": 0,
"pageSize": 20,
"pageNumber": 0,
"unpaged": false,
"paged": true
},
"totalPages": 2,
"totalElements": 32,
"last": false,
"size": 20,
"number": 0,
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"numberOfElements": 20,
"first": true,
"empty": false
}
So for this call, I have two pages and one each page we have 20 JSON entity is coming.
I wanted to call this same endpoint from the rest template.
Before pagination I used to call the same endpoint like this:
MyEntity[] responseEntity;
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept", MediaType.APPLICATION_JSON_VALUE);
// request entity is created with request headers
HttpEntity<MyEntity> requestEntity = new HttpEntity<>(requestHeaders);
Map<String, String> params = new HashMap<>();
params.put("feild1", val1);
params.put("feild2", val2);
responseEntity = restTemplate.getForObject(ApiEndpoint,MyEntity[].class,params);
As the endpoint was returning in the format of Array of MyEntity, above code as good enough. Now I have paginated rest endpoint.
How should I call the paginated endpoint and get the Array of MyEntity data again?
So far I have tried calling with: which is not working for me.
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(ApiEndpoint);
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
ResponseEntity<MyEntity[]> response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, requestEntity, MyEntity[].class);
Let me know if another way you have can be implemented here. Thanks for your help in advance.
What i did is, created the new class RestPageImpl
#JsonIgnoreProperties(ignoreUnknown = true)
public class RestPageImpl<ConfigurationTable> extends PageImpl<ConfigurationTable> {
private static final long serialVersionUID = -1423116752405536063L;
#JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public RestPageImpl(
#JsonProperty("content") List<ConfigurationTable> content,
#JsonProperty("number") int number, #JsonProperty("size") int size,
#JsonProperty("totalElements") Long totalElements, #JsonProperty("pageable") JsonNode pageable,
#JsonProperty("last") boolean last, #JsonProperty("totalPages") int totalPages,
#JsonProperty("sort") JsonNode sort, #JsonProperty("first") boolean first,
#JsonProperty("numberOfElements") int numberOfElements) {
super(content, PageRequest.of(number, size), totalElements);
}
public RestPageImpl(List<ConfigurationTable> content, Pageable pageable,
long total) {
super(content, pageable, total);
}
public RestPageImpl(List<ConfigurationTable> content) {
super(content);
}
public RestPageImpl() {
super(new ArrayList<>());
}
}
And on the controller side updated my code to:
MyEntity[] responseEntity =null;
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept", MediaType.APPLICATION_JSON_VALUE);
// request entity is created with request headers
HttpEntity<MyEntity> requestEntity = new HttpEntity<>(requestHeaders);
Map<String, String> params = new HashMap<>();
params.put("feild1", val1);
params.put("feild2", val2);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(ApiEndpoint);
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
ParameterizedTypeReference<RestPageImpl<MyEntity>> type = new ParameterizedTypeReference<RestPageImpl<MyEntity>>() {
};
responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, requestEntity, type);
And everything works fine now.
Related
I am trying to consume a webservice and post the JSON object as request in my program. JSON is nested.
{
"paymentorder": {
"operation": "Purchase",
"currency": "NOK",
"amount": 15610,
"vatAmount": 3122,
"description": "Test Purchase",
"userAgent": "Mozilla/5.0...",
"language": "nb-NO",
"urls": {
"hostUrls": ["https://localhost:9002", "https://powertools.local:9002"],
"completeUrl": "https://powertools.local:9002/payment-completed",
"cancelUrl": "https://powertools.local:9002/payment-canceled",
"callbackUrl": "https://powertools.local:9002/payment-callback",
"termsOfServiceUrl": "https://powertools.local:9002/termsandconditoons.pdf"
},
"payeeInfo": {
"payeeId": "20f3341c-e570-40a1-b76f-5347f4866de8",
"payeeReference": "P4555334",
"payeeName": "Kiran Vemula",
"productCategory": "P00432101",
"orderReference" : "P45553234"
},
"payer": {
"consumerProfileRef": "63adb0760ebdcca15d8475773a59c3f3b03df6222dfcc9f5740ce1eb3465f58e"
}
}
}
the build the Hashmaps like below:
private Map<String, Object> initiatePaymentMenuRequestBody(){
final Map<String, Object> paymentorderChilds = new LinkedHashMap<String, Object>();
paymentorderChilds.put("operation", "Purchase");
paymentorderChilds.put("currency",currency);
paymentorderChilds.put("amount",amount);
paymentorderChilds.put("vatAmount",vatAmount);
paymentorderChilds.put("description",description);
paymentorderChilds.put("userAgent",userAgent);
paymentorderChilds.put("language",language);
paymentorderChilds.put("urls", initiatePaymentMenuURLs());
paymentorderChilds.put("payeeInfo", initiatePaymentMenuPayeeInfo());
paymentorderChilds.put("payer", initiatePaymentMenuPayer());
return paymentorderChilds;
}
private Map initiatePaymentMenuURLs(){
final Map<String, Object> initiatePaymentMenuURLs = new LinkedHashMap<String, Object>();
List<String> hostUrls = new ArrayList<>();
hostUrls.add(mediqHostUrls1);
hostUrls.add(mediqHostUrls2);
initiatePaymentMenuURLs.put("hostUrls",hostUrls);
initiatePaymentMenuURLs.put("completeUrl",completeUrl);
initiatePaymentMenuURLs.put("cancelUrl",cancelUrl);
initiatePaymentMenuURLs.put("callbackUrl",callbackUrl);
initiatePaymentMenuURLs.put("termsOfServiceUrl",termsOfServiceUrl);
return initiatePaymentMenuURLs;
}
// implement this method with the real data from B2CCustomer and Cart object
private Map initiatePaymentMenuPayeeInfo(){
Map<String, String> initiatePaymentMenuPayeeInfo = new LinkedHashMap<String, String>();
initiatePaymentMenuPayeeInfo.put("payeeId",metchantID);
initiatePaymentMenuPayeeInfo.put("payeeReference",payeeReference);
initiatePaymentMenuPayeeInfo.put("payeeName",payeeName);
initiatePaymentMenuPayeeInfo.put("productCategory",productCategory);
initiatePaymentMenuPayeeInfo.put("orderReference",orderReference);
return initiatePaymentMenuPayeeInfo;
}
private Map initiatePaymentMenuPayer(){
Map<String, String> initiatePaymentMenuPayer = new LinkedHashMap<String, String>();
initiatePaymentMenuPayer.put("consumerProfileRef", initiateConsumerSession());
return initiatePaymentMenuPayer;
}
and finally calling the webservice to post the data:
#Override
public String initiatePaymentMenu(PaymentOrder paymentOrder1) {
final RestTemplate restTemplate2 = new RestTemplate();
final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host+initiatePaymentMenuhostpostfix);
Map paymentOrder = new HashMap();
paymentOrder.put("paymentorder", initiatePaymentMenuRequestBody());
final HttpEntity entity = new HttpEntity(paymentOrder,getHeadders());
LOG.info("initiatePaymentMenu===========> "+entity.getBody());
ResponseEntity<String> payExInitiatePaymentMenuResponse = restTemplate2.postForEntity(builder.build().encode().toUri(),entity,String.class);
LOG.info("initiatePaymentMenu" +payExInitiatePaymentMenuResponse.getStatusCode());
String returnString = payExInitiatePaymentMenuResponse.getStatusCode().toString();
return returnString;
}
Is I am doing the correct way? I am not getting the response and giving me 400 error. Is entity.getBody() prints the exact JSON? can I use it in postman to check the response?
Thanks in advance.
Solved. The web service is not accepting 2 different URLs in hostUrls field. It was a bug in the web service provider itself.
I have created an AEM servlet. Now I have created OSGi configuration (MULTI_FIELD as shown below of type array) with-in servlet itself because its defined as service also. Now, my requirement is how to access this osgi config inside doGet method. here is the code of servlet.
#Component(label = "Sample Servlet", description = "Sample Servlet", immediate = true, metatype = true)
#Service
#Properties(value = {
#Property(name = "sling.servlet.resourceTypes", value = {
GlobalConstants.RES_TYPE,
GlobalConstants.PAGE_RES_TYPE }, propertyPrivate =
true),
#Property(name = "sling.servlet.selectors", value = {
GlobalConstants.PAGES,
GlobalConstants.ASSETS }, propertyPrivate = true),
#Property(name = "sling.servlet.extensions", value = "xml",
propertyPrivate = true),
#Property(name = "sling.servlet.methods", value = { "GET" }) })
public class SampleServlet extends SlingAllMethodsServlet {
#Property(value={"English", "Hindi"}, unbounded =
PropertyUnbounded.ARRAY, label = "Subjects", cardinality = 50,
description = "Example for Multi field config")
private static final String MULTI_FIELD = "multifield";
#Override
protected void doGet(final SlingHttpServletRequest request, final
SlingHttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}
You need to set the value of property to a member field within activate method:
#Activate
protected void activate(final BundleContext bundleContext,
final Map<String, Object> componentConfig) {
this.multiField = (String[]) componentConfig.get(MULTI_FIELD);
}
I didn't try the code myself but it can give some idea. You can check Sling Main Servlet here to see how it works: http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.engine-2.2.6/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java
I agree with Emin that this should be handled inside the activation method. But on 6.2 the overwritten method has only one parameter and I would use PropertiesUtil instead of the cast.
The method would look like:
#Activate
public void activate(final ComponentContext componentContext) {
final Dictionary<?, ?> props = componentContext.getProperties();
this.multiField = Arrays.asList(PropertiesUtil.toStringArray(props.get(MULTI_FIELD), new String[0]));
}
Trying to send the request body with multi level data using rest-assured in the following way.
Request body:
{
"phoneNumber":1217071016,
"details":
[
{
"id":"123",
"name":"New",
"email":"hello#gmail.com"
},
{
"id":"234",
"name":"next",
"email":"next#gmail.com"
}
]
}
#Test public void generateToken() {
Map<String,String> userDetails = new HashMap<>();
userDetails.put("phoneNumber", "1217071016");
userDetails.put("details.Id", "241342");
userDetails.put("details.name", "New Name");
userDetails.put("details.email", "eclipse#test.com");
Response response = given()
.contentType("application/json")
.queryParam("access_token", "LL6rX8LRP7")
.body(userDetails)
.post("http://site/rest/try/update");
}
When sent in the above way, getting the bad request.
How to pass this kind of data in the above code
You need not a Map<String, String> but a Map<String, Object>:
Map<String, Object> userDetails = new HashMap<>();
Map<String, Object> details = new HashMap<>();
details.put("id", "241342");
details.put("name", "New Name");
details.put("email", "eclipse#test.com");
userDetails.put("phoneNumber", "1217071016");
userDetails.put("details", Arrays.asList(details, details));
I've been scouring the internet for resources and I feel like I almost have the answer, but I can't quite seem to get a BufferedImage to be returned to a browser window.
The project generates a maze which can then create a BufferedImage.
Here is the code from my Controller.
#RequestMapping(method = RequestMethod.GET, path = "/image", params = {"rows", "columns"})
public ResponseEntity<byte[]> image(#RequestParam(name = "rows") int rows, #RequestParam(name = "columns") int columns) throws IOException, InterruptedException {
try {
BasicCartesianGrid requestedMaze = new BasicCartesianGrid(rows, columns);
requestedMaze.forEach(CellAlgorithms.BINARY_TREE);
BufferedImage bufferedImage = requestedMaze.toDisplayImage();
{ // Dumping to file for debugging <- this works as expected
File outputFile = new File("save.png");
ImageIO.write(bufferedImage, "png", outputFile);
}
ByteArrayOutputStream pngByteStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", pngByteStream);
byte[] pngBytes = pngByteStream.toByteArray();
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentLength(pngBytes.length);
headers.setCacheControl(CacheControl.noCache().getHeaderValue());
return new ResponseEntity<>(pngBytes, headers, HttpStatus.OK);
} catch (Exception e) {
// This hasn't occurred yet, but is for just in case
Thread.sleep(1000);
System.err.println(e.getLocalizedMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<>(e.getLocalizedMessage().getBytes("ASCII"), headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I have ascertained that the PNG is being generated correctly, as the file exists and is viewable on my hard-drive. My browser gets a broken image returned back. From my terminal, I can get some more information.
curl "http://localhost:8080/maze/image?rows=10&columns=10"
Dumps out the following (the quotation marks are part of the response, while the data represented by the ellipsis changes from request to request, due to the fact each maze is randomly generated and unique):
"iVBORw0KGgoAAAANSUhEUgAAA......"
I googled this string prefix, and found this page. Which shows that this string should be used as a data-uri, like so:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA…" >
I'm not sure where to go from here. It seems like my image is being generated correctly, but I must be missing a header in my response to tell the browser/spring that these bytes should be interpreted as an image and not as just a string.
UPDATE:
Based on the dialog between myself and Shawn Clark from the answer section, here is what I have presently.
#SpringBootApplication
#Log4j
public class SpringMazesApplication {
#Bean
public HttpMessageConverter<BufferedImage> bufferedImageHttpMessageConverter() {
log.debug("Registering BufferedImage converter");
return new BufferedImageHttpMessageConverter();
}
public static void main(String[] args) throws IOException {
SpringApplication.run(SpringMazesApplication.class, args);
}
}
And the actual controller:
#Controller
#RequestMapping(path = "/maze/basic", method = RequestMethod.GET)
#Log4j
public class BasicMazeController {
#RequestMapping(params = {"format", "format=text"}, produces = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String simpleMazeText(#RequestParam(name = "rows", defaultValue = "10", required = false) int rows,
#RequestParam(name = "columns", defaultValue = "10", required = false) int columns) throws IOException {
BasicCartesianGrid requestedMaze = new BasicCartesianGrid(rows, columns);
requestedMaze.forEach(CellAlgorithms.BINARY_TREE);
return requestedMaze.toDisplayString();
}
#RequestMapping(params = {"format=image"}, produces = MediaType.IMAGE_PNG_VALUE)
#ResponseBody
public BufferedImage simpleMazeImage(#RequestParam(name = "rows", defaultValue = "10", required = false) int rows,
#RequestParam(name = "columns", defaultValue = "10", required = false) int columns) throws IOException {
log.debug("Starting image generation");
BasicCartesianGrid requestedMaze = new BasicCartesianGrid(rows, columns);
requestedMaze.forEach(CellAlgorithms.BINARY_TREE);
BufferedImage bufferedImage = requestedMaze.toDisplayImage();
{ // Dumping to file for debugging <- this works as expected
log.debug("Dumping image to hd");
File outputFile = new File("save.png");
ImageIO.write(bufferedImage, "png", outputFile);
}
log.debug("Returning from image generation");
return bufferedImage;
}
#RequestMapping
#ResponseBody
public ResponseEntity<String> simpleMazeInvalid(#RequestParam(name = "rows", defaultValue = "10", required = false) int rows,
#RequestParam(name = "columns", defaultValue = "10", required = false) int columns,
#RequestParam(name = "format") String format) throws IOException {
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<>("Invalid format: " + format, headers, HttpStatus.BAD_REQUEST);
}
}
From my terminal I can curl -D - "url" and I can see with both logging/debugging and the output from my terminal, that the converter is properly registered at the begging of the application and that I'm getting responses as you would expect from all but the actual image uri which returns a 406 Not Acceptable. If I remove the #ResponseBody from the image method, it just returns a 500. I can verify that the image is properly generated as it is being written to disk as I expect it should.
Check out the produces attribute on the #RequestMapping. You would want to set it to image/png.
Here is a complete example:
#RestController
public class ProduceImage {
#GetMapping(path = "/image", produces = "image/png")
public BufferedImage image() throws Exception {
BufferedImage bufferedImage = ImageIO.read(new File("E:\\Downloads\\skin_201305121633211421.png"));
return bufferedImage;
}
}
My BufferedImage is something from my computer but it can be just as easily the BufferedImage that you have from the requestedMaze.toDisplayImage() without having to do all that other work. To make this work you want to include the BufferedImageHttpMessageConverter in your context.
#Bean
public HttpMessageConverter<BufferedImage> bufferedImageHttpMessageConverter() {
return new BufferedImageHttpMessageConverter();
}
We are facing our REST API versioning and after having read a lot about the different options (URI versioning, mime type versioning) have decided to use the latter approach.
I was expecting that Springfox generates the following doc:
v1:
get /api/architecture/mails - application/vnd.arch.mails.v1+json
get /api/architecture/services - application/vnd.arch.service.v1+json
v2:
get /api/architecture/services - application/vnd.arch.service.v2+json
However, in the v2 I also get this:
get /api/architecture/services - application/vnd.arch.service.v1+json
It shouldn't be there since I configured the v2 Docklet with
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"}))) so that it filters the services according to the versioned mime type. Why isn't is working?
Here is our springfox config:
#Bean
public Docket arqV1Api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v1+json","application/vnd.arch.mail.v1+json"})))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v1 group");
}
#Bean
public Docket arqV2Api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v2 group");
}
And these is the REST Controller:
private static final String serviceArqV1MediaType = "application/vnd.arch.service.v1+json";
private static final String serviceArqV2MediaType = "application/vnd.arch.service.v2+json";
private static final String mailsArqV1MediaType = "application/vnd.arch.mail.v1+json";
#ApiOperation(value = "Gets architecture services",
notes = "",
produces = serviceArqV1MediaType)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Request OK"),
#ApiResponse(code = 400, message = "Bad Request")})
#RequestMapping(value = {"/services"}, method = RequestMethod.GET,
produces = serviceArqV1MediaType)
public List<ServicioArquitectura> getServices() {
return Arrays.asList(new ServiceArch[]{new ServicioArquitectura("Support"), new ServicioArquitectura("Kickoff")});
}
#ApiOperation(value = "Gets architecture services",
notes = "",
produces = serviceArqV2MediaType)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Request OK"),
#ApiResponse(code = 400, message = "Bad Request")})
#RequestMapping(value = {"/services"}, method = RequestMethod.GET,
produces = {serviceArqV2MediaType})
public List<ServicioArquitecturaV2> getServicesV2() {
return Arrays.asList(new ServiceArchV2[]{new ServiceArchV2("Support", Boolean.TRUE), new ServiceArchV2("Kickoff", Boolean.FALSE)});
}
#ApiOperation(value = "Gets mails",
produces = mailsArqV1MediaType)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Request OK"),
#ApiResponse(code = 400, message = "Bad Request")})
#RequestMapping(value = {"/mails"}, method = RequestMethod.GET,
produces = {mailsArqV1MediaType})
public List<String> getMails() {
return Arrays.asList(new String[]{"xxxcompany.com"});
}
I opened an issue in Springfox's github and they pointed out how to correctly configure it. Here's the solution:
Helper static methods:
public static Predicate<RequestHandler> withMediaType(final MediaType[] mediaTypes){
return new Predicate<RequestHandler>() {
#Override
public boolean apply(RequestHandler input) {
if(mediaTypes!=null){
ProducesRequestCondition producesCondition = input.getRequestMapping().getProducesCondition();
Set<MediaType> producibleMediaTypes = producesCondition.getProducibleMediaTypes();
for (MediaType mt : producibleMediaTypes) {
for (int i = 0; i < mediaTypes.length; i++) {
if(mt.equals(mediaTypes[i])){
return true;
}
}
}
}
return false;
}
};
}
public static Set<String> mediaTypesToStringSet(MediaType[] mediaTypes){
Set<String> mediaTypesSet = new HashSet<String>();
if(mediaTypes!=null){
for (int i = 0; i < mediaTypes.length; i++) {
mediaTypesSet.add(mediaTypes[i].toString());
}
}
return mediaTypesSet;
}
Docket definitions:
#Bean
public Docket arqV1Api() {
MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v1+json"),
new MediaType("application","vnd.arch.mails.v1+json")};
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(withMediaType(validMediaTypes))
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
.produces(mediaTypesToStringSet(validMediaTypes))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v1 group");
}
#Bean
public Docket arqV2Api() {
MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v2+json")};
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(withMediaType(validMediaTypes))
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
.produces(mediaTypesToStringSet(validMediaTypes))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v2 group");
}