Spring ModelAndView to RestController json response - spring

I have a legacy spring code where they use ModelAndView and they add the objects to it as below.
ModelAndView result = new ModelAndView();
result.addObject("folders", folders);
return result;
for the above i am getting response as
{
"folders": [
{
"recordCount": 0,
"folderContentType": "Reports",
"folderId": 34,
},
{
"recordCount": 2,
"folderContentType": "SharedReports",
"folderId": 88,
}
]
}
I have changed these to use Spring's RestController with a POJO backing the results returned from DB.
#GetMapping("/folders")
public List<Folder> getAllFolders() {
return Service.findAllFolders(1,2);
}
This returns a JSON as below
[
{
"folderId": 359056,
"folderName": "BE Shared Report Inbox",
"folderDescription": "BE Shared Report Inbox",
},
{
"folderId": 359057,
"folderName": "BE Shared Spec Inbox",
}]
How could i return this as exactly as my legacy code response. I know i can convert the List to Map and display. But, is there any equivalent
way.
Thanks.

You can put your result into a map.
#GetMapping("/folders")
public List<Folder> getAllFolders() {
return Service.findAllFolders(1,2);
}
Change to:
#GetMapping("/folders")
public Map<String,List<Folder>> getAllFolders() {
Map<String,List<Folder>> map = new HashMap<>();
map.put("folders",Service.findAllFolders(1,2));
return map;
}

Related

Webflux subscribe to nested Publishers and serialize them to JSON

I have a UserDto with related items taken from repository which is Project-Reactor based, thus returns Flux/Mono publishers.
My idea was to add fields/getters to DTO, which themselves are publishers and lazily evaluate them (subscribe) on demand, but there is a problem:
Controller returns Flux of DTOs, all is fine, except spring doesn't serialize inner Publishers
What I'm trying to achieve in short:
#Repository
class RelatedItemsRepo {
static Flux<Integer> findAll() {
// simulates Flux of User related data (e.g. Orders or Articles)
return Flux.just(1, 2, 3);
}
}
#Component
class UserDto {
// Trying to get related items as field
Flux<Integer> relatedItemsAsField = RelatedItemsRepo.findAll();
// And as a getter
#JsonProperty("related_items_as_method")
Flux<Integer> relatedItemsAsMethod() {
return RelatedItemsRepo.findAll();
}
// Here was suggestion to collect flux to list and return Mono
// but unfortunately it doesn't make the trick
#JsonProperty("related_items_collected_to_list")
Mono<List<Integer>> relatedItemsAsList() {
return RelatedItemsRepo.findAll().collectList();
}
// .. another user data
}
#RestController
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#GetMapping
Flux<UserDto> dtoFlux() {
return Flux.just(new UserDto(), new UserDto(), new UserDto());
}
}
And this is the response I get:
HTTP/1.1 200 OK
Content-Type: application/json
transfer-encoding: chunked
[
{
"related_items_as_method": {
"prefetch": -1,
"scanAvailable": true
},
"related_items_collected_to_list": {
"scanAvailable": true
}
},
{
"related_items_as_method": {
"prefetch": -1,
"scanAvailable": true
},
"related_items_collected_to_list": {
"scanAvailable": true
}
},
{
"related_items_as_method": {
"prefetch": -1,
"scanAvailable": true
},
"related_items_collected_to_list": {
"scanAvailable": true
}
}
]
It seems like Jackson doesn't serialize Flux properly and just calls .toString() on it (or something similar).
My question is: Is there existing Jackson serializers for Reactor Publishers or should I implement my own, or maybe am I doing something conceptually wrong.
So in short: how can I push Spring to evaluate those fields (subscribe to them)
If I understand correctly, what you try to achieve is to create an API that needs to respond with the following response:
HTTP 200
[
{
"relatedItemsAsField": [1,2,3]
},
{
"relatedItemsAsField": [1,2,3]
},
{
"relatedItemsAsField": [1,2,3]
}
]
I would collect all the elements emitted by the Flux generated by RelatedItemsRepo#findAll by using Flux#collectList, then map this to set the UserDto object as required.
Here is a gist.

.NET Core 3.1 API works fine locally but doesn't after deployment

In a Web-Application, written in C# in .NET Core 3.1, i want to Display a Datatable of all the Blobs in one of our Company's Azure Blob Storages. For this i'm using Datatables.net in the Frontend with Ajax-Calls which target a selfwritten API in the same Web-App. The API should get all the searched Blobs, format them for easier viewing and then give them back to the Table. Locally it really works like a charm. However, soon after deployment i noticed that the Ajax-Call just simply returns a 404 Response.
For reference:
My API-Controller
[Route("Blob/api/[controller]")]
[ApiController]
public class BlobsController : ControllerBase
{
private readonly string _connectionString;
private readonly string _container;
private readonly BlobContainerClient _client;
public BlobsController(IConfiguration configuration)
{
IEnumerable<IConfigurationSection> _blobStorageSection = configuration.GetSection("BlobStorage").GetChildren();
_connectionString = _blobStorageSection.Single(e => e.Key == "ConnectionString").Value;
_container = _blobStorageSection.Single(e => e.Key == "ContainerName").Value;
_client = new BlobContainerClient(_connectionString, _container);
}
[HttpGet("{EncodedDelimiter}/{EncodedPrefix}")]
public ActionResult GetBlobs(string EncodedDelimiter, string EncodedPrefix)
{
if (! StorageIsAvailable())
return NotFound();
string Delimiter = WebUtility.UrlDecode(EncodedDelimiter);
string Prefix = WebUtility.UrlDecode(EncodedPrefix);
Pageable<BlobHierarchyItem> BlobHierarchy = _client.GetBlobsByHierarchy(delimiter: Delimiter, prefix: Prefix);
return Ok(EnrichBlobList(BlobHierarchy));
}
[HttpGet("init/{EncodedDelimiter}")]
public ActionResult Initialize(string EncodedDelimiter)
{
if (! StorageIsAvailable())
return NotFound();
string Delimiter = WebUtility.UrlDecode(EncodedDelimiter);
Pageable<BlobHierarchyItem> BlobHierarchy = _client.GetBlobsByHierarchy(delimiter: Delimiter);
return Ok(EnrichBlobList(BlobHierarchy));
}
Here the Ajax-Call Snippet
var Table = $("#BlobTable").DataTable({
ajax:{
url: "api/Blobs/init/%2F",
dataSrc: ""
},
processing: true,
columnDefs:[
{
"targets": 0,
"data": "standartizedName",
},
{
"targets": 1,
"data": null,
"render": function(full){
return renderTyp(full);
},
"width": "10%"
},
{
"targets": 2,
"data": null,
"render": function(full){
return renderDatum(full);
},
"width": "15%"
},
{
"targets": 3,
"data": null,
"render": function(full){
return renderAction(full);
},
"orderable": false,
"searchable": false,
"width": "10%"
}
],
order:[[1, "desc"]],
pageLength: 50
});
And bc i have seen similar Problems where the Source-Problem was in StartUp:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestDbcontext>(options => options.UseSqlServer(Configuration.GetConnectionString("TestDB"),
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromMinutes(5),
errorNumbersToAdd: null
);
}));
if (Env.IsDevelopment())
{
UseFakeAuthenticationAndAuthorization(services);
//UseAuthenticationAndAuthorization(services, Configuration);
}
else
{
//UseFakeAuthenticationAndAuthorization(services);
UseAuthenticationAndAuthorization(services, Configuration);
}
services.AddControllersWithViews();
RepositoriesTestGUI(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Home}/{id?}");
});
}
Has someone else an Idea what could be the Problem? An API-Call is an absolute Necessity since the Storage contains 70k Files
EDIT:
By Request here are the Network Details
NetworkDetails
Bc this is my first Post i can't put embedded Pictures in apparently
The root cause of this problem is not having permission to access. The easiest way to test is to change the access level to Container.
My test steps:
Access a picture in container( Blob or Container access level ).
Uri like:
https://testaccount.blob.core.windows.net/testblob/SignalR.png
Result
Access a picture in container( private access level ). We will reproduce the issue.
Suggestion
We can write a method to generate sas token to access the files. You can refer my code or read the official doc.

Spring rest api same url for bulk and single request

I would like to have this API in a Spring boot app:
POST /items
{
"name": "item1"
}
POST /items
[
{
"name": "item1"
},
{
"name": "item2"
},
]
So the same endpoint could accept array or a single element in a json body.
Unfortunatelly this doesn't work:
#PostMapping(path="items")
public ResponseEntity<String> items(#RequestBody Item item) {}
#PostMapping(path="items")
public ResponseEntity<String> items(#RequestBody List<Item> items) {}
I also tried this:
#PostMapping(path="items")
public ResponseEntity<String> items(#RequestBody #JsonFormat(with= JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) List<Item> items) {}
it doesn't work.
If I wrap the list like:
public class Items {
#JsonFormat(with= JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<item> items;
}
then unfortunately my request body would look like:
{
"items": [
{
"name": "item1"
},
{
"name": "item2"
},
]
}
Do you know how I can have such API with spring boot?
You want a variable that can directly hold an Array or an Object. There is no straightforward way to achieve something like that because of the Static Typing restrictions of Java.
The only way I can imagine so far to achieve something like this is to create a single API that takes some generic type.
Like an Object:
#PostMapping(path="items")
public ResponseEntity<String> items(#RequestBody Object body) {
if(body instanceof List) {
} else {
}
}
Or a String:
#PostMapping(path="items")
public ResponseEntity<String> items(#RequestBody String body) {
if(body.charAt(0) == '[' && body.charAt(body.length() - 1) == ']') {
} else if(body.charAt(0) == '{' && body.charAt(body.length() - 1) == '}') {
} else {
}
}
You'll have to do a lot of work manually.
Also, you can't create two APIs with the same path /items and the same method POST, they will raise a compile-time error.

How to hide json field when using Springdata pageable?

Currently I'm using SpringData to build my restful project.
I'm using Page findAll(Pageable pageable, X condition, String... columns); ,this method .The result looks like this:
{
"content": [
{
"id": 2,
"ouId": 1,
"pClassId": 3,
"isPublic": 0,
"accessMethod": 3,
"modifierName": null
}
],
"last": true,
"totalPages": 1,
"totalElements": 3,
"number": 0,
"size": 10,
"sort": [
{
"direction": "DESC",
"property": "id",
"ignoreCase": false,
"nullHandling": "NATIVE",
"ascending": false,
"descending": true
}
],
"first": true,
"numberOfElements": 3
}
The question is how to hide some specific json field in the content ?
And #JsonIgnore annotation is not flexible , the fields I need in different APIs are different.
I tried to write an annotation ,but when processing the Page ,I found that the content is unmodifiable .
So , hope that someone could help me with it.
If you don't want to put annotations on your Pojos you can also use Genson.
Here is how you can exclude a field with it without any annotations (you can also use annotations if you want, but you have the choice).
Genson genson = new Genson.Builder().exclude("securityCode", User.class).create();
// and then
String json = genson.serialize(user);
OR using flexjson
import flexjson.JSONDeserializer;
import flexjson.JSONSerializer;
import flexjson.transformer.DateTransformer;
public String toJson(User entity) {
return new JSONSerializer().transform(new DateTransformer("MM/dd/yyyy HH:mm:ss"), java.util.Date.class)
.include("wantedField1","wantedField2")
.exclude("unwantedField1").serialize(entity);
}
You have to use a custom serialize like the following:
#JsonComponent
public class MovieSerializer extends JsonSerializer<Movie> {
#Override
public void serialize(Movie movie, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
// The basic information of a movie
jsonGenerator.writeNumberField("id", movie.getId());
jsonGenerator.writeStringField("name", movie.getName());
jsonGenerator.writeStringField("poster", movie.getPoster());
jsonGenerator.writeObjectField("releaseDate", movie.getReleaseDate());
jsonGenerator.writeObjectField("runtime", movie.getRuntime());
jsonGenerator.writeStringField("storyline", movie.getStoryline());
jsonGenerator.writeStringField("rated", movie.getRated());
jsonGenerator.writeNumberField("rating", movie.getRating());
jsonGenerator.writeEndObject();
}
}
And then annotate your model class with: #JsonSerialize(using = MovieSerializer.class)

Consuming REST Service in Spring

I'm frightfully new to Spring and Java but I'm trying to consume some code for some rule validations in Easy Rules but I can't quite figure it out.
#RequestMapping(method = {RequestMethod.GET}, value = "author/field", produces= MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ResponseEntity<Enum> getField(#RequestParam(value="field", required=true) String field){
Enum enum = mongoService.findByField(field);
if(enum == null){
return new ResponseEntity<Enum>(HttpStatus.NO_CONTENT);
}else{
return new ResponseEntity<Enum>(enum,HttpStatus.OK);
}
}
So I'm trying something like:
import com.mongoservice.Enum
import com.mongoservice.Enums
RestTemplate restTemplate = new RestTemplate();
String uri = "http://localhost:9000";
//This is my confusion
List<Enums> response = restTemplate.getForObject(uri +
"/author/field?={field}", Enum.class,"a").getEnums();
String value = response.getValue().toString().trim();
//this is the record i'm checking against that is pulling a specific string value and what i'm expecting
String record = "a";
return (value == record);
The JSON data I'm trying to pull back is modeled like this but I need to validate to make sure that record equals one of the values from enums[] json array
{
"field": "a",
"descriptor": "blah",
"enums": [
{
"value": "h",
"description": "blah"
},
{
"value": "e",
"description": "blah"
},
{
"value": "l",
"description": "blah"
},
{
"value": "p",
"description": "blah"
}
]
}
What is the problem that you are seeing is it just not matching? If so it could be because you are using == instead of String.equals. Try modifying your code to:
return record.equals(value);
See Java String.equals versus == for more.
Can you change String uri = "http://localhost:9000"
and missed the path variable name field it should be like author/field?field={field} as per your controller description.

Resources