Descending sorting LDAP with Spring Boot - java

Is it possible to sort records from LDAP directory with SortControlDirContextProcessor descending, not ascending?
I based on this:
SpringLdap - LdapTemplateSortedSearchITest
public void testSearch_SortControl_ConvenienceMethod() {
SortControlDirContextProcessor requestControl;
// Prepare for first search
requestControl = new SortControlDirContextProcessor("cn");
tested.search(BASE, FILTER_STRING, searchControls, callbackHandler,
requestControl);
int resultCode = requestControl.getResultCode();
boolean sorted = requestControl.isSorted();
assertThat("Search result should have been sorted: " + resultCode, sorted).isTrue();
List list = callbackHandler.getList();
assertSortedList(list);
}
It works but ascending. How to set descending sort?

I believe it could be helpful:
public SortKey(String attrID,
boolean ascendingOrder,
String matchingRuleID)
Creates a sort key for an attribute. Entries will be sorted according to the specified attribute in the specified sort order and using the specified matching rule, if supplied.
Parameters:
attrID - The non-null ID of the attribute to be used as a sort key.
ascendingOrder - If true then entries are arranged in ascending order. Otherwise there are arranged in descending order.
matchingRuleID - The possibly null ID of the matching rule to use to order the attribute values. If not specified then the ordering matching rule defined for the sort key attribute is used.
It is from docs: Java doc
Regarding your question: Example code from java doc
And I found one more alternative called unboundid ldap sdk
link

The solution for me was to implement a custom DirContextProcessor that allowed me to sort on multiple attributes with the desired direction (ascending/descending) by making use of the overload method of the SortControl class that takes an array of SortKeys objects as a parameter.
The implementation must extend the AbstractFallbackRequestAndResponseControlDirContextProcessor and override the createRequestControl method.
The superclass AbstractFallbackRequestAndResponseControlDirContextProcessor will take care of the actual creation of the control. It only expects 2 pieces of information from the subclass.
The fully qualified class name of the control to instantiate
The types and values of the constructor parameters
The fully qualified class name is provided in the subclass property defaultRequestControl, and the types and values of the constructor parameters are provided in the subclass method createRequestControl.
The information of the sorting direction for any particular attribute is provided in the ascendingOrder property of the SortKey object.
public class SortMultipleControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor{
private SortKey[] sortKeys;
private boolean sorted;
private int resultCode;
public SortMultipleControlDirContextProcessor(SortKey ... sortKeys){
if(ArrayUtils.isEmpty(sortKeys)){
throw new IllegalArgumentException("At least one key to sort on must be provided.");
}
this.sortKeys = sortKeys;
this.sorted = false;
this.resultCode = -1;
this.defaultRequestControl = "javax.naming.ldap.SortControl";
this.defaultResponseControl = "javax.naming.ldap.SortResponseControl";
this.fallbackRequestControl = "com.sun.jndi.ldap.ctl.SortControl";
this.fallbackResponseControl = "com.sun.jndi.ldap.ctl.SortResponseControl";
loadControlClasses();
}
#Override
public Control createRequestControl(){
return super.createRequestControl(new Class[]{SortKey[].class, boolean.class}, new Object[]{sortKeys, critical});
}
#Override
protected void handleResponse(Object control) {
Boolean result = (Boolean) invokeMethod("isSorted", responseControlClass, control);
this.sorted = result;
Integer code = (Integer) invokeMethod("getResultCode", responseControlClass, control);
this.resultCode = code;
}
public SortKey[] getSortKeys(){
return sortKeys;
}
public boolean isSorted(){
return sorted;
}
public int getResultCode(){
return resultCode;
}
}
After the implementation, you can use the class to sort the results on multiple attributes in any desired direction:
// SortKey for sorting results on the cn attribute in descending order
SortKey cnSortKey = new SortKey("cn", false, null);
// Instantiate the control
SortMultipleControlDirContextProcessor myCustomControl = new SortMultipleControlDirContextProcessor(cnSortKey);
// Perform the search with the control
List<User> users = ldapTemplate.search("", orFilter.encode(), searchControls, new UserAttributesMapper(), myCustomControl);

Related

Sort ArrayList of Objects by Specific Element

I have several arraylists which each contain player data for a specific team. Each object contains the following elements in order; Jersey Number, First Name, Last Name, Preferred Position, Goals, Assists. The user decides whether to view the data by goals or assists, and then the data is displayed in descending order. Goals and assists are both of int data type.
I will be able to display the data fine but what I am stuck on is how to sort the arrayList by one of these specific stats. Because the data from all the teams is in different arrayLists, and need to be sorted all together, do I need to combine the arrayLists into one master arrayList that will be sorted? As for the sorting, I have done a bit of research and it looks like I need to use a comparator? Could someone provide some assistance with this because I have never used these before and am quite lost. Examples would be great.
I have attached a few code snippets to hopefully provide some clarity.
ArrayList <blackTeam> blackTeam = new ArrayList <blackTeam>();
ArrayList <blueTeam> blueTeam = new ArrayList <blueTeam>();
ArrayList <greenTeam> greenTeam = new ArrayList <greenTeam>();
ArrayList <orangeTeam> orangeTeam = new ArrayList <orangeTeam>();
ArrayList <redTeam> redTeam = new ArrayList <redTeam>();
ArrayList <yellowTeam> yellowTeam = new ArrayList <yellowTeam>();
private void displaystatButtonActionPerformed(java.awt.event.ActionEvent evt) {
//sort arrayList by goals/assists
}
EDIT:
This is how my classes are set up, as well as how data is added to them. Hopefully this clears up some questions.
//add data to database
black = new blackTeam(jerseyNum, firstName, lastName, prefPosition, goals, assists);
blackTeam.add(black);
class blackTeam {
int goals, assists;
String jerseyNum, firstName, lastName, prefPosition;
blackTeam (String _jerseyNum, String _firstName, String _lastName, String _prefPosition, int _goals, int _assists) {
jerseyNum = _jerseyNum;
firstName = _firstName;
lastName = _lastName;
prefPosition = _prefPosition;
goals = _goals;
assists = _assists;
}
}
I have one these classes for each team.
I suggest using Comparator on your object, let me assume it is Team
public class Team{
private int jerseyNumber;
private String lastName;
...
public int getJerseyNumber(){
return jerseyNumber;
}
}
If you want to sort based on jersey number, generate JeseryNumberComaparator:
import java.util.Comparator;
public class JeseryNumberComaparator implements Comparator {
#Override
public int compare(Team t1, Team t2) {
// descending order (ascending order would be:
// t1.getJerseyNumber()-t2.getJerseyNumber())
return t1.getJerseyNumber()-t2.getJerseyNumber()
}
}
It will sort your list based on jersey number by:
Collections.sort(blackTeam, new JerseyNumberComparator());
For sorting Collection in Descending order (other than their natural sort order), you have to define your own Comparator.
For sorting on a specific field individually (one at a time), you have to define separate Comparator implementation.
In your class, you can define two individual Comparators. Here is example code.
static final Comparator<Team> SORT_TEAM_BY_GOALS_DESCENDING = new Comparator<Team>(){
public int compare(Team t1, Team t2){
return t2.getGoals() - t1.getGoals();
}
}
static final Comparator<Team> SORT_TEAM_BY_ASSIST_DESCENDING = new Comparator<Team>(){
public int compare(Team t1, Team t2){
return t2.getAssist() - t1.getAssist();
}
}
Make sure that, normal sort is always natural order, in your case for int it is always Ascending. In order to have Descending order, you need to do t2 - t1. t1 - t2 will give you natural Ascending order.
Now in order to use this Comparator, just use following code.
Collections.sort(team, SORT_TEAM_BY_GOALS_DESCENDING);
or
Collections.sort(team, SORT_TEAM_BY_ASSIST_DESCENDING);
And off course, if all these different color List (i.e. blackTeam and so on) are only for specific team identified by color, than add one more field to your Team class called 'color` which will identify each player along with what team they belongs to.
As long as your 6 classes blackTeam to yellowTeam all descend from the same parent, ie. that they are declared like this:
public class blackTeam extends Team { ... }
then you can make a new ArrayList<Team> and add them all to it:
ArrayList<Team> all = new ArrayList<>();
all.addAll(blackTeam);
all.addAll(blueTeam);
all.addAll(yellowTeam);
// etc...
Then you can sort this list using an instance of Comparator<Team>. Since Java8, however, there's a much neater way to create a comparator using lambda expressions:
all.sort((a, b) -> a.getScore() - b.getScore()); // or whatever attribute you want to compare on
If you want to do it the old fashioned way instead, then you can create an anonymous class like this:
all.sort(new Comparator<Team>() {
#Override
public int compare(Team a, Team b) {
return a.getScore() - b.getScore();
}
});
They amount to the same thing, but the lambda based approach is a bit less wordy!
Note that i suspect you don't actually want to have 6 different classes for the different colours. Are you sure you have understood the role of a class properly?

using set or List here in the code

I am newbie to java, I have a scenario, where i need to list the organisation types from the table:
Requirement : Just listing, no add or removing the elements,
As i understand the difference between set and list:
Set:
Set is Unique collection of Objects.
Set is Un-ordered collection of Objects.
List:
List is non-unique collection of Objects.
List is ordered collection of Objects.
In my table i am having columns like:
id name is_active
1 Lab 1
2 Pharmacy 2
3 Hospital 3
Maximum 10 rows
**Controller**:
List<OrgType> orgTypeList = organizationService.getAllOrgTypes(true);
OrgTypeResponse response = new OrgTypeResponse();
List<EntityDetail> orgTypeDetailList = new ArrayList<>();
EntityDetail orgTypeDetail;
for(OrgType orgType : orgTypeList) {
orgTypeDetail = new EntityDetail();
orgTypeDetail.setId(orgType.getId());
orgTypeDetail.setName(orgType.getName());
orgTypeDetailList.add(orgTypeDetail);
}
response.setStatus(ResponseStatusCode.SUCCESS);
response.setTotalOrgTypes((long)orgTypeDetailList.size());
response.setOrgTypes(orgTypeDetailList);
return response;
**Service** Implementaion:
List<OrgType> orgTypeList = orgTypeRepository.findByActive(active);
return orgTypeList;
This is my EntityDetail class:
public class EntityDetail {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My question here is, can i use the Set instead of List
If Set is used, can i use TreeSet, because i need to show in the asc order of id
Or Leave the code, as it is
i just want the clarification,
Thanks
You can use any of them but things to be kept in consideration:
Set although provides unique data, but that also has a cost.
In case, you are sure that table has unique names of organizations then you should opt for list.
It seems like you are using Spring with JPA, if that is the case, then you can use SORT interface(org.springframework.data.domain.Sort) to get sorted data.
My question here is, can i use the Set instead of List
Yes, without problem, just implement methods equals and hashCode.
If Set is used, can i use TreeSet, because i need to show in the asc order of id
You can if class EntityDetail implements interface Comparable<EntityDetail>. This is necessary because TreeSet must know what is the natural order of the various EntityDetail objects.
For more details please see Oracle docs on object ordering and Javadoc for Comparable
yes u can use SET instead of List in this scenario because SET will ensure that duplicate entries are eliminated. But making use of SET make sure that you have overridden "equals" and "hashcode" appropriately.
This is how you need to override equals and hashcode methods and for sorting purpose you need to implement Comparable and implement compareTo method as follows:
class EntityDetail implements Comparable<EntityDetail>{
#Override
public int hashcode(){
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + id;
return result;
}
#Override
public boolean equals(Object o){
if (o == this) return true;
if (!(o instanceof EntityDetail)) {
return false;
}
EntityDetail ed = (EntityDetail) o;
return ed.name.equals(name) &&
ed.id == id ;
}
#Override
public int compareTo(EntityDetail ed) {
int compareId = ((EntityDetail) ed).getId();
//ascending order
return this.id - compareId;
//descending order
//return compareId - this.id;
}
}
You can use List if you can make sure in your code that the details are added in it in the order that you want. If you are not sure of the order in which you add then you can use the Collections.sort method. For this you will also want to make your OrgType implement the Comparable interface to provide a strategy to order the OrgType objects. In your case it is by id.
If you use TreeSet, the sorting is done automatically whenever you insert into the set thereby eliminating the use of Collections.sortbut you will still have to provide an ordering strategy.
Have a look at this
There are costs of using a Set because it maintains unique elements but because you have a maximum of 10 rows that won't be a problem.

Hibernate Search 5.5.3 - Sort by collection size no longer working

I've been trying to migrate from Hibernate Search 5.5.2 to 5.5.3, and I've run into an issue with one of my sort fields. This is the code that was working with 5.5.2 (or maybe it wasn't working, and just wasn't throwing errors?)
public class CollectionCountBridge implements MetadataProvidingFieldBridge {
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field(name, FieldType.INTEGER).sortable(true);
}
#Override
public void set(String name, Object object, Document document, LuceneOptions luceneOptions) {
if (object == null || (!(object instanceof Collection))) {
return;
}
Collection<?> coll = (Collection<?>) object;
int size = coll.size();
IntField field = new IntField(name, size, (luceneOptions.getStore() != Store.NO) ? Field.Store.YES : Field.Store.NO);
document.add(field);
}
}
...
#Field(analyze = Analyze.NO, norms = Norms.YES, index = Index.YES)
#FieldBridge(impl = CollectionCountBridge.class)
#IndexedEmbedded
#OneToMany
public Set<MyCollection> getMyCollection() {
return myCollection;
}
The code essentially stores the size of the collection as a sortable field. This was based on the documentation which suggested if I need to define a sortable field via a bridge, then I have to implement MetadataProvidingFieldBridge to mark it sortable. The documentation however shows only an example for a string field, whereas I need to use a numeric field. http://docs.jboss.org/hibernate/search/5.5/reference/en-US/html_single/#sortablefield-annotation
So following the upgrade to 5.5.3 I started getting errors like:
org.hibernate.search.exception.SearchException: HSEARCH000307: Sort type INT is not compatible with string type of field 'myCollection'
I've tried adding the field to the document in a variety of ways, and nothing seems to work. Some things I've tried:
luceneOptions.addNumericFieldToDocument(name, size, document);
document.add(new SortedNumericDocValuesField(name, size));
//this throws an error on index
java.lang.IllegalArgumentException: cannot change DocValues type from SORTED_NUMERIC to NUMERIC for field "myCollection"
public class CollectionCountBridge extends NumberBridge
So, my question is, what is the correct way to add a sortable numeric field to the index, via a bridge, as of 5.5.3?
In fact, it's not a bug.
You have to add a corresponding NumericDocValuesField to your document to enable sorting. We will improve that in the future but for now, that's what you have to do.
Additionally, I wouldn't recommend you to add the field with the same name as the default one, you'd better index the collection size with another field name.
Your FieldBridge should look like this:
public class CollectionCountBridge implements MetadataProvidingFieldBridge {
private static final String COUNT_SUFFIX = "_count";
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field(name + COUNT_SUFFIX, FieldType.INTEGER).sortable(true);
}
#Override
public void set(String name, Object object, Document document, LuceneOptions luceneOptions) {
if (object == null || (!(object instanceof Collection))) {
return;
}
Collection<?> coll = (Collection<?>) object;
int size = coll.size();
luceneOptions.addNumericFieldToDocument(name + COUNT_SUFFIX, size, document);
document.add(new NumericDocValuesField(name + COUNT_SUFFIX, size.longValue()));
}
}
And when sorting, use a new SortField( "myCollection_count", SortField.Type.LONG ) ).
It seems you've hit a bug here. For a custom bridge as yours we are failing to properly detect the numeric encoding type. I've filed HSEARCH-2292 for this.
As a work-around, you may create a transient property in your entity which exposes the collection size. To this property, you add #Field and #SortableField which should add the required fields to the index, using the correct types.

Find index of an object in a list

I have situation where I have a list(required items) that holds a table column result like:
NAME
ADDRESS
AGE
.
.
etc
In my method I get a User object that contains values for user.getName(), user.getAge() etc. I want to know the best way to ensure that every item in the list is present in the user object. The no of items in the list are variable.
public boolean isUserInfoComplete(User user, ArrayList list){
//so, if the list has AGE, the user.getAge() must have some value
}
One way I thought of is maintaining another list that holds values of every user info and checking that against my db list but that is not scalable.
It's not possible to dynamically match your method names with the list contents without reflection (which can be expensive and fragile). You may want to consider keeping your User values in a central Map cache. Here's one way to do that:
public class User {
private enum Field {
NAME,
AGE
//...
}
private Map<String, Object> values = new HashMap<>();
private void putValue(Field field, Object value) {
values.put(field.name(), value);
}
private Object getValue(Field field) {
return values.get(field.name());
}
public void setName(String name) {
putValue(Field.NAME, name);
}
public String getName() {
return (String)getValue(Field.NAME);
}
public void setAge(int age) {
putValue(Field.AGE, age);
}
public Integer getAge() {
return (Integer)getValue(Field.AGE);
}
//...
public boolean isUserInfoComplete(List<String> fields) {
return values.keySet().containsAll(fields);
}
}
You could use reflection to solve this problem if the items in the list match the getters in your User object.
For example, if AGE is in the list, you could use reflection to look for the getAge() method on the User class, call it on the object, and then check the result for null (or switch on the method return type to perform other types of checks).
Here's a starting point for you to experiment with (I haven't compiled or tested it):
public boolean isUserInfoComplete(User user, ArrayList list){
for(String attribute : list) {
String methodName = "get" + attribute.substring(0, 1).toUpperCase() + attribute.substring(1).toLowerCase();
Method method = User.class.getMethod(methodName, null);
if(method != null) {
Object result = method.invoke(user);
if(result == null) {
return false;
}
}
}
return true;
}
This seems like a case where you need reflection. This gives you the opportunity to inspect methods and field from your objects at runtime.
If you know your User-objects etc will follow a java bean standard then you will be able to use the getters for checking, though I see now problem in making your fields public final and checking directly on the fields themselves.
Take a look at https://docs.oracle.com/javase/tutorial/reflect/
You can check it using contains() while looping. This process will be very resource-consuming.
Maybe you can redesign something and simply compare two User objects? Will be faster. You can do it by providing your own implementation of equals and hashcode methods.

Java How To Keep Unique Values In A List of Objects

I currently am retrieving a list of objects List<NprDto> (The NprDto class contains accountId, theDate1, and theDate2) from a query that returns results where the NprDto has duplicate accountIds. I need to have a List<NproDto> of only unique accountIds but keep the object. It only needs to add the first accountId it comes across and ignores the rest.
I'm currently trying this:
private List<NprDto> getUniqueAccountList(List<NprDto> nonUniqueAccountList) throws Exception {
Map<Long,NprDto> uniqueAccountsMapList = new HashMap<Long,NprDto>();
List<NprDto> uniqueAccountsList = null;
if(nonUniqueAccountList != null && !nonUniqueAccountList.isEmpty()) {
for(NprDto nprDto : nonUniqueAccountList) {
uniqueAccountsMapList.put(Long.valueOf(nprDto.getAccountId()), nprDto);
}
}
uniqueAccountsList = new ArrayList<NprDto>(uniqueAccountsMapList.values());
return uniqueAccountsList;
}
But this doesn't seem to be working because when I iterate through the returned uniqueAccountsList later it only picks up the first object.
Any help would be greatly appreciated.
I need to have a List of only unique accountIds but keep the
object.
You should use Set<NprDto>. For that you need to override equals and hasCode at NproDto class.
class NprDto{
Long accountId;
.......
#Override
public boolean equals(Object obj) {
NproDto other=(NproDto) obj;
return this.accountId==other.accountId;
}
#Override
public int hashCode() {
return accountId.hashCode();
}
}
Change your getUniqueAccountList as follows:
private Set<NprDto> getUniqueAccountSet(){
Map<Long,NprDto> uniqueAccountsMapList = new HashMap<Long,NprDto>();
Set<NprDto> uniqueAccs = new HashSet<NprDto>(uniqueAccountsMapList.values());
return uniqueAccs;
}
What you need here is a LinkedHashSet. It removes duplicates and keeps insertion order.
You do not need TreeSet here because it sorts and changes the order of the original List.
If preserving insertion order is not important use a HashSet.
Actually you need to implements equals and hascode method, it will be good for you
Remove duplicates from a list
Java Set contains the Unique value but its unsorted collection. List is sorted collection but contains the duplicates objects.
What you need to do is implement the equals, hashCode and compareTo methods for NprDto to match two objects as equal when their ID is the same. Then you can filter all duplicates as easy as this:
private List<NprDto> getUniqueAccountList(List<NprDto> nonUniqueAccountList) {
return new ArrayList<NprDto>(new LinkedHashSet<NprDto>(nonUniqueAccountList));
}

Categories

Resources