How to create an EntityModel for type with nested resources using Spring HATEOAS? - spring-boot

I have an address entity model, EntityModel<Address>:
{
"id" : 10662,
"streetNumber" : 4823,
"steetName" : "Bakersfield",
"city" : "anytown",
"state" : "anystate",
"zip" : "12345",
"_links" : {
"self" : {
"href" : "http://dev:9001/api/addresses/10662"
},
"address" : {
"href" : "http://dev:9001/api/addresses/10662"
}
}
}
I have a Java client that gets this address and assigns it to a new student:
EntityModel<Address> address = client.getAddress(10662);
Student student = new Student();
student.setAddress(address.getContent());
//set other student props
I would like to have the client then send a RESTful POST request to the server to save the student. But how can I create a EntityModel with a link to the address?
I know how to set links at the Student level
EntityModel<Student> studentEntity = EntityModel.of(
student,
WebMvcLinkBuilder.linkTo(student.getId()).withSelfRel()
);
but not sure how to set a link to the student's address

Related

java 11 collect to map by keeping value unique

I have a specific json value as shown below,
{
"record_id" : "r01",
"teacherNstudents": [
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "steve",
"sid" : "S01"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "natasha",
"sid" : "S02"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "bruce",
"sid" : "S03"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "victor",
"sid" : "S04"
}
},
{
"teacher" : {
"name" : "henry",
"tid" : "T02"
},
"student" : {
"name" : "jack",
"sid" : "S05"
}
},
{
"teacher" : {
"name" : "henry",
"tid" : "T02"
},
"student" : {
"name" : "robert",
"sid" : "S06"
}
}
]
}
I am trying to generate a map like the one below,
[ {"S01", "T01"} , {"S05","T02"} ]
This is by removing all duplicate values and selecting only one teacher and student. The current code I wrote for this is
var firstMap = records.getTeacherNstudents()
.stream()
.collect(Collectors.toMap(tS -> tS.getTeacher().getTid(),
tS -> tS.getStudent().getSid(),
(a1, a2) -> a1));
return firstMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
I believe this can be improved, by using Collectors.groupingBy. I am still working on it, but if anyone has any good idea on how to solve this, please share.
Using Java 8 groupingBy
You can try the below approach in order to have the Map<String,List<String>> or Map<String,Set<String>>(avoid duplicates) where key of map will be the teacher id and value as List or Set of Students corresponding to each teacher.
I have used groupingBy feature from java 8 and did the grouping based on the tId and before collecting it, I have downstream it to List or Set of student Ids corresponding to each tId.
Approach A: Map<String,Set< String >> (Uniques)
data.getTeacherStudentMappingList()
.stream()
.collect(Collectors.groupingBy(x -> x.getTeacher().getTid(), LinkedHashMap::new,
Collectors.mapping(y -> y.getStudent().getSid(),Collectors.toSet())));
Approach B : Map<String,List< String >> (Non-uniques, duplicates)
data.getTeacherStudentMappingList()
.stream()
.collect(Collectors.groupingBy(x -> x.getTeacher().getTid(), LinkedHashMap::new,
Collectors.mapping(y -> y.getStudent().getSid(),Collectors.toList())));
Here,
data is the converted object from the given json.
LinkedHashmap::new is used to preserve the order of student data from the json in the output.
collectors.mapping is used to convert the values corresponding to each key into the student ids.
Collectors.toList() will collect the list of student ids in the list.
Collectors.toSet() will collect the unique student ids in the set.
Output:
{T01=[S01, S02, S03, S04], T02=[S05, S06]}

Monitor backpressure count and size in custom processor

I have a custom processor (NiFi 1.8.0) that already modifies incoming flow files as needed. However before transferring the file to the outgoing relationship I would like to check if that relationship's backpressure is close to exceeding it's threshold. If it is then I plan to send the flow file to another relationship that connects to a PutFile processor where it will be written to disk.
I know I can get the incoming queue count and size. But I can't figure out how to get count and size from the outgoing relationship's connection.
There is a controller service available called - SiteToSiteStatusReportingTask which essentially sends the status of each and every event that is happening in Nifi.
If you look at the data structure it returns , you can see it has few very helpful attributes on detecting backpressure -
// fields for connections
{ "name" : "sourceId", "type" : ["string", "null"]},
{ "name" : "sourceName", "type" : ["string", "null"]},
{ "name" : "destinationId", "type" : ["string", "null"]},
{ "name" : "destinationName", "type" : ["string", "null"]},
{ "name" : "maxQueuedBytes", "type" : ["long", "null"]},
{ "name" : "maxQueuedCount", "type" : ["long", "null"]},
{ "name" : "queuedBytes", "type" : ["long", "null"]},
{ "name" : "backPressureBytesThreshold", "type" : ["long", "null"]},
{ "name" : "backPressureObjectThreshold", "type" : ["long", "null"]},
{ "name" : "backPressureDataSizeThreshold", "type" : ["string", "null"]},
{ "name" : "isBackPressureEnabled", "type" : ["string", "null"]},
You can use this information to derive what you need. Refer this article for more details on implementation
I ended up finding the connections from the ProcessGroupStatus object:
String myProcessorId = this.getIdentifier();
int queuedCount = 0;
float queuedBytes = 0;
ProcessGroupStatus processGroupStatus = ((EventAccess) getControllerServiceLookup().getControllerStatus();
if (processGroupStatus.getConnectionStatus() != null {
Collection < CollectionStatus > groupConnections = processGroupStatus.getConnectionStatus();
// Now have to iterate through groupConnections to find the one where the connection's source ID = myProcessorId and
// the connection's name = 'normal output' (this is the name of a relationship I added)
ArrayList connections = new ArrayList <> (groupConnections);
for (Object processorConnection : connections) {
ConnectionStatus connection = (ConnectionStatus) processorConnection;
if (connection.getName().equals("normal output") && connections.getSourceId.equals(myProcessorId)) {
// Now I can grab the current count and size of the 'normal output' relationship
// The back pressure threshold values can be grabbed from the connection as well
queuedCount = connection.getQueuedCount();
queuedBytes = connection.getQueuedBytes();
break;
}
}
}
The above only retrieves connections from the parrent group. If the connection you're looking for is contained in a child group, you will need to iterate through the child groups:
ProcessGroupStatus processGroupStatus = ((EventAccess) getControllerServiceLookup().getControllerStatus();
ArrayList childProcessorGroups = new ArrayList < > (processGroupStatus.getProcessGroupStatus());
for (Object childProcessorGroup : childProcessorGroups) {
ProcessGroupStatus childProcessGroupStatus = (ProcessGroupStatus) childProcessorGroup;
Collection < CollectionStatus > groupConnections = childProcessGroupStatus.getConnectionStatus();
// Then iterate through groupConnections as above
}
The NiFi getControllerServiceLookup() does show an 'allConnections' variable which contains all connections across all processors in all groups. But there doesn't appear to be a getter for it. If there was a getter for it, you wouldn't have to worry about which group to look in for connections. You could simply iterate through 'allConnections' and look for the connection matching your processor ID and relationship name.

Spring mongodb Find document if a single field matches in a list within a document

I have some data stored as
{
"_id" : ObjectId("abc"),
"_class" : "com.xxx.Team",
"name" : "Team 1",
"members" : [
{"userId" : 1, "email" : "a#x.com" },
{"userId" : 2, "email" : "b#x.com" },
]
}
{
"_id" : ObjectId("xyz"),
"_class" : "com.xxx.Team",
"name" : "Team 2",
"members" : [
{"userId" : 2, "email" : "b#x.com" },
{"userId" : 3, "email" : "c#x.com" }
]
}
I have 2 POJO classes Team (mapped to entire document),TeamMember (mapped to members inside a document).
Now I want to find to which team a specific user belongs to. For example if I search for a#x.com it should return me the document for Team 1. Similarly searching for b#x.com should return both of them as its in both the documents.
As I am very new to spring, not able to find out how to solve this.
Note: I am using MongoTemplate
somthing like this will do
final QueryBuilder queryBuilder = QueryBuilder.start();
//queryBuilder.and("members.email").is("a#x.com") This will work as well. try it out.
queryBuilder.and("members.email").in(Arrays.asList("a#x.com"))
final BasicDBObject projection = new BasicDBObject();
projection.put("fieldRequired", 1);
try (final DBCursor cursor = mongoTemplate.getCollection(collectionName).find(queryBuilder.get(), projection)
.batchSize(this.readBatchSize)) {
while (cursor.hasNext()) {
DBObject next = cursor.next();
........
// read the fields using next.get("field")
.........
}
}
batchsize and projection is not mandatory. Use projection if you don't want to fetch the whole document. You can specify which field in the document you want to fetch in the result.
You can use below code with the MongoTemplate
Query findQuery = new Query();
Criteria findCriteria = Criteria.where("members.email").is("b#x.com");
findQuery.addCriteria(findCriteria);
List<Team> teams = mongoTemplate.find(findQuery, Team.class);

Pagingandsortingrepositrory in spring data , primary key in the entity is not present in the response

I have declared pagingandSortingRepository for a JPA entity and the response to findall() is below
"_embedded" : {
"assetDashboardCustomers" : [ {
"utilization" : "80",
"_links" : {
"self" : {
"href" : "http://localhost:8080/utilbycustomer/Customer01"
},
"assetDashboardCustomer" : {
"href" : "http://localhost:8080/utilbycustomer/Customer01"
}
}
},
I do not want _links and self [HATEOAS] details, but just the plain JSON.
What is the property I should set so that I get a plain JSON in the response
The first comment answers your first question - you cannot turn off
the links. The second comment answers you 2nd question which is how to
have the ID in the JSON body.
--- By Alan hay in the comments

Spring Data Rest and excerpt Projections

I am using the inlineAddress sample of the Spring Data Rest documentation.
/persons return the address inline as expected.
Now I add a projection to the AddressRepository
#RepositoryRestResource(excerptProjection = AddressProjection.class)
Which is as below
#Projection(name = "AddressesProjection", types = Address.class)
public interface AddressProjection {
public String getStreet();
}
This is causing the /persons call to have an address projection as _embedded
{
"_embedded" : {
"persons" : [ {
"firstName" : "dfdf",
"lastName" : "2",
"addresses" : [ {
"street" : "tx",
"state" : "tx",
"country" : "dfd"
} ],
"_embedded" : {
"addresses" : [ {
"street" : "tx",
"_links" : {
"self" : {
"href" : "/api/addresses/1{?projection}",
"templated" : true
}
}
} ]
},
"_links" : {
"self" : {
"href" : " api/persons/1{?projection}",
"templated" : true
},
"addresses" : {
"href" : " /api/persons/1/addresses"
}
}
} ]
}
}
I dont know if this is expected. This behaviour is causing repeated information when I have a oneToMany relation like order/Comments and have projection on both order and comments and when I access order/1/comments I see the order also embedded for each comments.
I have a similar issue with spring-data-rest 2.5.6. So I'd like to add this.
If :
an A entity has a #OneToMany relationship to a B entity
the B entity's repository #RepositoryRestResource contains an excerptProjection
Then spring-data-rest will embed the B entity's list in any A entity (in _embedded).
If there is no excerptProjection, the list won't be embedded.
I'd like to be able to choose what I want to be embedded, but at moment, I found no solution to do so.
For anybody that is looking for the answer to this issue, I actually did find a solution.
Based on the example in Spring-RestBucks you will need to have a custom RepresentationModelProcessor on A entity.
Also consider the official Spring HATEOAS Documentation on RepresentationModelProcessor.
Applying to the above example, you would do:
public class PersonRepresentationProcessor implements RepresentationModelProcessor<EntityModel<Person>> {
private final EntityLinks entityLinks;
#Override
public EntityModel<Person> process(EntityModel<Person> personModel) {
// create new EntityModel without the embedded collection
TypedEntityLinks<Person> typedPersonLink = entityLinks.forType(Person::getId);
EntityModel<Person> newPerson = new EntityModel<>(personModel.getContent(),
personModel.getLinks());
// add more links or other modifications
return newPerson;
}
}

Resources