Retrieve id of an entity based on which the stream is being mapped - java-8

I'm trying to construct a stream pipeline which is not really straightforward and it gets me puzzled.
The idea is that I have a class containing a set of entities I want to traverse. Application class defines a field Set<Document> documents. Inside of these docs I have a field DocumentFile documentFile. I filter the stream based on the name of this document file, but the result that I need is the id of Document entity.
So the method goes like this:
private long retrieveSmth(String docName, long applicationId) {
final Application application = this.applicationDao.get(applicationId);
final long docId = application.getDocuments()
.stream()
.map(Document::getDocumentFile)
.filter(doc -> doc.getDocumentFileName().equals(docName))
...
}
At this point I get stuck questioning myself how do I get the control back to Document level and retrieve the id of the document whose document file satisfies the condition. Is there a way to do this using Stream API?

If you map() to DocumentFile you cannot "go back" to the owner object of DocumentFile : the Stream<Document> was transformed into a Stream<DocumentFile>.
You should so not map and specify the object to test from Stream<Document>.filter() :
final long docId = application.getDocuments()
.stream()
.filter(doc -> doc.getDocumentFile().getDocumentFileName().equals(docName))
.map(Document::getId); // now it is possible
Note that you should avoid talking to strangers and this makes this bad smell :
doc.getDocumentFile().getDocumentFileName()
So it would be interesting to introduce a method matchesName() in Document that does the delegation and the equality test :
public boolean matchesName(String name){
return name.equals(getDocumentFile().getDocumentFileName());
}
In this way it sounds better :
.filter(doc -> doc.matchesName(docName))

Related

How to GroupBy objects from a list by some common catalog of properties in Java 8

I've been struggling with a problem with one of my lists of data because one of the requirements after generating it is to group some of them by some common parameters (more than 1)
What I should get at the end is a map where the value is a list of common objects. For example.
List<Cause> listToGroup = new ArrayList<>();
listToGroup.add(Similar);
listToGroup.add(Common);
listToGroup.add(Similar);
listToGroup.add(Similar);
listToGroup.add(Common);
In a weird way to represent one group (Similar) and the other (Common), those should be separated into two different lists (that list is generated by a request to other methods, in that case, I just added manually to show what could be the contained data in the list). My main problem is the criteria to group them because is based on a group of parameters that are shared, but not all (if the required parameters are equal, should belong to the same list) In the class shown below, that behaviour is seen because there are some parameters that are not being considered.
public class Cause extends GeneralDomain {
//parameters which must be equals between objects
private Long id;
private Date creationDate;
private Part origin;
private Part destination;
//parameters which are not required to be equal
private BigDecimal value
private Stage stageEvent
//omitted getters and setters
}
I've been seeing the comparator method and the groupingBy method provided in Java 8, but at the moment I just know how to perform that task considering just one parameter (for example grouping them by id) And I have no idea about how to group them using more than one parameter.
//this should be the code if the requirement would be just one parameter to groupby, but in my case are more than one.
Map<Long, List<Cause>> result = request.getList(criteria)
.stream()
.map(p -> parsin.createDto(p))
.collect(groupingBy(Cause ::getId));
I would be really glad for any suggestion. If my explanation is not clear, I'm so sorry. That became so complicated that even is hard for me to explain

How to get spring neo4j cypher custom query to populate an array of child relationships

Built-in queries to Spring Data Neo4j (SDN) return objects populated with depth 1 by default. This means that "children" (related nodes) of an object returned by a query are populated. That's good - there are actual objects on the end of references from objects returned by these queries.
Custom queries are depth 0 by default. This is a hassle.
In this answer, it is described how to get springboot neo4j to populate a related element to the target of a custom query - to achieve an extra one level of depth of results from the query.
I am having trouble with this method when the related elements are in a list:
#NodeEntity
public class BoardPosition {
#Relationship(type="PARENT", direction = Relationship.INCOMING)
public List<BoardPosition> children;
I have a query returning a target BoardPosition and I need it's children to be populated.
#Query("MATCH (target:BoardPosition) <-[c:PARENT]- (child:BoardPosition)
WHERE target.play={Play}
RETURN target, c, child")
BoardPosition findActiveByPlay(#Param("Play") String play);
The problem is that the query appears to return one separate result for each child, and those results aren't being used to populate the array of children in the target.
Instead of Spring Neo collating the children into the array on the target, I get "only 1 result expected" error - as if the query is returning multiple results each with one child, rather than one result with the children in it.
org.springframework.dao.IncorrectResultSizeDataAccessException:
Incorrect result size: expected at most 1
How can I have a custom query to populate that target's children list?
(Note that the built-in findByPlay(play) does what I want - the built-in queries have a depth of 1 rather than 0, and it returns a target with populated children - but of course I need to make the query a bit more sophisticated than just "by Play"... that's why I need to solve this)
Versions:
org.springframework.data:spring-data-neo4j:5.1.3.RELEASE
neo4j 3.5.0
=== Edit ======
Your problem arises because you have self-relationship (relationship between nodes of the same label)
This is how Spring treat your query for single node:
org.springframework.data.neo4j.repository.query.GraphQueryExecution
#Override
public Object execute(Query query, Class<?> type) {
Iterable<?> result;
....
Object ret = iterator.next();
if (iterator.hasNext()) {
throw new IncorrectResultSizeDataAccessException("Incorrect result size: expected at most 1", 1);
}
return ret;
}
Spring passes your node class type Class<?> type to neo4j-ogm and have your data read back.
You know, neo4j server will returns multiple rows for your query, one for each matching path:
A <- PARENT - B
A <- PARENT - C
A <- PARENT - D
If your nodes are of different labels, i.e. of different class type then the ogm only return single node correspond to your query return type, no problem.
But your nodes are of the same labels, i.e. same class type => Neo4j OGM cannot distinguish which is the returned node -> All nodes A, B, C, D returned -> Exception
Regard this issue, I think you should file a bug report now.
For workaround, you can can change the query to return only the distinct target.your_identity_property (identity_property is 'primary key' of the node, which uniquely identify your node)
Then have your application call load with the that identity property:
public interface BoardRepository extends CrudRepository<BoardPos, Long> {
#Query("MATCH (target:B) <-[c:PARENT]- (child:B) WHERE target.play={Play} RETURN DISTINCT target.your_identity_property")
Long findActiveByPlay(#Param("Play") String play);
BoardPos findByYourIdentityProperty(xxxx);
}
=== OLD ======
Spring docs says that (highlighted by me):
Custom queries do not support a custom depth. Additionally, #Query does not support mapping a path to domain entities, as such, a path should not be returned from a Cypher query. Instead, return nodes and relationships to have them mapped to domain entities.
So clearly your use-case (populate children nodes by custom query) is supported. Spring framework already maps the results into a single node. (Indeed, my setup on local turnouts that the operation is working properly)
So your exception may be caused by several issues:
You have more than one target:BoardPosition with target.play={play}. So the exception refers to more than one target:BoardPosition instead of one BoardPosition with multiple child result
You have incorrect entity mapping. Do you have your mapping field annotated with #Relationship with correct direction attribute? You might post your entity here.
Here is my local setup:
#NodeEntity(label = "C")
#Data
public class Child {
#Id
#GeneratedValue
private long id;
private String name;
#Relationship(type = "PARENT", direction = "INCOMING")
private List<Parent> parents;
}
public interface ChildRepository extends CrudRepository<Child, Long> {
#Query("MATCH (target:C) <-[p:PARENT]- (child:P) "
+ "WHERE target.name={name} "
+ "RETURN target, p, child")
Child findByName(#Param("name") String name);
}
(:C) <-[:PARENT] - (:P)
Consider the alternative query
MATCH (target:BoardPosition {play:{Play}})
RETURN target, [ (target)<-[c:PARENT]-(child:BoardPosition) | [c, child] ]
which is using list comprehension to return not only the target but also its relations and related nodes of label BoardPosition within one result row. This ensures that the result will be a single row (as long as your attribute play is unique).
I didn't try it with your example but in my application this approach is working fine. Neo4j OGM hydrates the objects as expected. It is important to include the related nodes as well as the relations pointing to the nodes.
If you enable neo4j OGM logs, you can see that the build-in queries with depth 1 use the same approach.

Realm Xamarin LINQ Select

Is there a way to restrict the "columns" returned from a Realm Xamarin LINQ query?
For example, if I have a Customer RealmObject and I want a list of all customer names, do I have to query All<Customer> and then enumerate the results to build the names list? That seems cumbersome and inefficient. I am not seeing anything in the docs. Am I missing something obvious here? Thanks!
You have to remember that Realm is an object based store. In a RDBMS like Sqlite, restricting the return results to a sub-set of "columns" of an "record" makes sense, but in an object store, you would be removing attributes from the original class and thus creating a new dynamic class to then instantiate these new classes as objects.
Thus is you want just a List of strings representing the customer names you can do this:
List<string> names = theRealm.All<Customer>().ToList().Select(customer => customer.Name).ToList();
Note: That you take the Realm.All<> results to a List first and then using a Linq Select "filter" just the property that you want. Using a .Select directly on a RealmResults is not currently supported (v0.80.0).
If you need to return a complex type that is a subset of attributes from the original RealObject, assuming you have a matching POCO, you can use:
var custNames = theRealm.All<Customer>().ToList().Select((Customer c) => new Name() { firstName = c.firstName, lastName = c.lastName } );
Remember, once you convert a RealmResult to a static list of POCOs you do lose the liveliness of using RealmObjects.
Personally I avoid doing this whenever possible as Realm is so fast that using a RealmResult and thus the RealObjects directly is more efficient on processing time and memory overhead then converting those to POCOs everytime you need to new list...

How to find a template in Alfresco with OpenCMIS from Spring application?

I am working in a Spring 3.1 application and I need to find a String template document located in Alfresco's repository. I can already create a file in alfresco with OpenCMIS, but I couldn't figure out how to find a template, so if anyone knows how to do it or point me an example, please let me know, thanks in advance!
There are a number of options you can use. First of all, you need to have a criteria that uniquely identifies your document. Here below I'll show some, hopefully your case falls in one of them or they will inspire you towards a proper solution. The following uses pseudo code, please have a look to the OpenCMIS dev guide for working with the Java client API.
BY ID
Once you create a Document via CMIS, you get the unique ID of it that you can store in your application for later retrieval.
Map<String, Object> templateProperties = createDocumentProperties();
Folder folder = getTemplatesFolder();
ObjectId templateId = createTemplateIn(folder);
storeTemplateId(templateId.getId(), templateProperties); // persist the ID
...
// later on
...
String id = getTemplateId(); // retrieve the ID
Session session = openCMISSession();
Document template = (Document)session.getObject(id);
BY PATH
Similar to the previous example, you will have to take note of where you stored the document instead of its ID, or having a way to construct the path by hand.
String path = getTemplatePath(); // either recover it from DB or construct a path
Document template = (Document)session.getObjectByPath(path);
BY PROPERTY VALUE
Let's say you can use a specific metadata field on a template Document that allows you to easily retrieve it afterwards (e.g. you created some specific Alfresco metadata model for your use case).
String meta = TemplateProperties.TEMPLATE_ID; // e.g. my:templateId
String type = TemplateProperties.TEMPLATE_TYPE; // e.g. my:template
String templateMeta = "TEMPLATE1";
Map<String, Object> templateProperties = createDocumentProperties();
templateProperties.put(meta, templateMeta);
templateProperties.put(PropertyIds.OBJECT_TYPE_ID, type);
createTemplate(templateProperties);
...
// later on
...
String type = TemplateProperties.TEMPLATE_TYPE; // e.g. my:template
String meta = TemplateProperties.TEMPLATE_ID;
String tplId = "TEMPLATE1";
String query = String.format("SELECT * FROM % WHERE % = '%'", type, meta, tplId);
ItemIterable<QueryResult> i = session.query(query, false);
QueryResult qr = i.iterator().next(); // let's assume we have one single match
String templateId = qr.getPropertyByQueryName("cmis:objectId").getFirstValue()
Document template = (Document)session.getObject(templateId);
BY QUERY
The previous approach is not really tied to search by property name, and can be easily extended to use any kind of query that identifies your templates. Have a look at the Alfresco page on its CMIS query language implementation details to learn more ways of querying the repository.

best practice Treeview populating from differents kinds of objects

I would like to populate a Treeview.
Here is what I have in DB :
table : Box
BoxID
BoxName
table Book :
BookID
BookName
BoxID (fk Box.BoxID)
table Chapter:
ChapterID
ChapterName
BookID (fk Book.BookID)
As you may know a treeview is made up of treenode object.
A treenode object have a text property and a tag property.
The "text" property is the text that it's display on the screen for this node and the "tag" is an "hidden" value (usually uses to identify a node)
So in my case; the fields ending with ID will be used in the "tag" property and the fields ending with Name will be used in the "text" property
example :
so for a book; I will use the BookID field for the "tag" property and BookName field for the "text" property
note : I use a dbml so I have a Book object, Box object and Chapter object and I use linq to get them from the db.
So my question is; what is the best practice to build this tree?
I have a solution but it's really ugly because it looks like I'm duplicating the code.
The problem is that the values I need to extract for the text and tag properties are identified by differents fields name in the db
So for the book level, I need to get the BookID field to populate the tag property of my node; for the box level, I need to get the BoxID field to populate the tag property , ....
How can I make a kind of generic way to do it ?
I hope I made myself clear enough, don't hesitate to ask me questions :)
Thx in advance
Here is what I have for the moment
I get the list of box with a linq (dbml) request.
List<Box> MyListofBox = getMyListofBox();
Treenode tnBox = null;
Treenode tnBook =null;
foreach(Box b in MyListofBox )
{
tnBox = new TreeNode();
tnBox.tag = b.BoxID;
tnBox.text = b.BoxName;
List<Book> MyListofBook = getMyListofBookByBoxID(b.BoxID)
foreach(Book boo in MyListofBook )
{
tnBook = new TreeNode();
tnBook.tag = boo.BookID;
tnBook.text = boo.BookName;
tnBox.nodes.add(tnBook);
}
mytreeview.nodes.add(tnBox);
}
but of course I don't like this solution...
do you have a better way ?
I would extract the you need from the database in the form of a struct, possibly via the anonnoumous type that has been added to C# together with linq. Then I would populate insert this data into the place in the tree.
From what I get, you are trying to get each property separately, which will not work so well, because then you will have to make a call to the database for each separate property, which is very wasteful.
Addition based on what you have added
I do not believe the code can be more compact - the names you call are similar, but not the same and the way you do it was what I was trying to explain earlier.
You could
Define an key/value interface that both Box and Book implement
Define a delegate that returns a TreeNode and create delegate methods that accept Box and Book
However, I think the code is fine as written. Sometimes you just have to code it and there's little point in further abstracting or optimizing it.
The only issue I see in the code is that you're making a database call in a loop. Whether or not that's a problem depends on the application.

Resources