Quellcode durchsuchen

support for multiple sort orders

tags/2.0.1
Jonathan Cobb vor 4 Jahren
Ursprung
Commit
021f0bf38c
7 geänderte Dateien mit 110 neuen und 73 gelöschten Zeilen
  1. +3
    -3
      wizard-common/src/main/java/org/cobbzilla/wizard/model/ldap/LdapEntity.java
  2. +24
    -46
      wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchQuery.java
  3. +30
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchSort.java
  4. +16
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SortOrder.java
  5. +2
    -1
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractDAO.java
  6. +26
    -16
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/SqlViewSearchHelper.java
  7. +9
    -7
      wizard-server/src/main/java/org/cobbzilla/wizard/ldap/LdapServiceBase.java

+ 3
- 3
wizard-common/src/main/java/org/cobbzilla/wizard/model/ldap/LdapEntity.java Datei anzeigen

@@ -6,8 +6,8 @@ import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.SingletonSet;
import org.cobbzilla.util.reflect.ReflectionUtil;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.UniquelyNamedEntity;
import org.cobbzilla.wizard.model.search.SortOrder;

import javax.persistence.Transient;
import java.util.*;
@@ -195,8 +195,8 @@ public abstract class LdapEntity extends UniquelyNamedEntity {

private static Map<String, Comparator<LdapEntity>> comparatorCache = new ConcurrentHashMap<>();

public static Comparator<LdapEntity> comparator (final String field, SearchQuery.SortOrder order) {
final SearchQuery.SortOrder sort = order == null ? SearchQuery.SortOrder.ASC : order;
public static Comparator<LdapEntity> comparator (final String field, SortOrder order) {
final SortOrder sort = order == null ? SortOrder.ASC : order;
final String cacheKey = field + ":" + order;
Comparator<LdapEntity> comp = comparatorCache.get(cacheKey);
if (comp == null) {


+ 24
- 46
wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchQuery.java Datei anzeigen

@@ -1,6 +1,5 @@
package org.cobbzilla.wizard.model.search;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -11,12 +10,9 @@ import org.cobbzilla.util.collection.ArrayUtil;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.util.json.JsonUtil;
import org.cobbzilla.util.string.StringUtil;
import org.cobbzilla.wizard.model.BasicConstraintConstants;
import org.cobbzilla.wizard.validation.ValidEnum;

import java.util.Arrays;

import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.wizard.model.Identifiable.CTIME;

@@ -38,14 +34,6 @@ public class SearchQuery {
public static final int MAX_SORTFIELD_LENGTH = 50;
public static final String DEFAULT_SORT_FIELD = CTIME;

public enum SortOrder {
ASC, DESC;
@JsonCreator public static SortOrder create(String val) { return valueOf(val.toUpperCase()); }
public boolean isAscending () { return this == ASC; }
public boolean isDescending () { return this == DESC; }
}
public static final String DEFAULT_SORT = SortOrder.DESC.name();

public static final SearchQuery DEFAULT_PAGE = new SearchQuery();
public static final SearchQuery FIRST_RESULT = new SearchQuery(1, 1);
public static final int INFINITE = Integer.MAX_VALUE;
@@ -63,16 +51,16 @@ public class SearchQuery {
this.setPageNumber(other.getPageNumber());
this.setPageSize(other.getPageSize());
this.setFilter(other.getFilter());
this.setSortField(other.getSortField());
this.setSortOrder(other.getSortOrder());
this.setSorts(other.getSorts());
this.setBounds(other.getBounds());
}

public SearchQuery(Integer pageNumber, Integer pageSize, String sortField, String sortOrder, String filter, NameAndValue[] bounds) {
if (pageNumber != null) setPageNumber(pageNumber);
if (pageSize != null) setPageSize(pageSize);
if (sortField != null) this.sortField = sortField;
if (sortOrder != null) this.sortOrder = SortOrder.valueOf(sortOrder).name();
if (sortField != null) {
addSort(new SearchSort(sortField, SortOrder.fromString(sortOrder)));
}
if (filter != null) this.filter = filter;
this.bounds = bounds;
}
@@ -94,7 +82,7 @@ public class SearchQuery {
}

private static String normalizeSortOrder(SortOrder sortOrder) {
return (sortOrder == null) ? SearchQuery.DEFAULT_SORT : sortOrder.name();
return (sortOrder == null) ? SortOrder.DEFAULT_SORT : sortOrder.name();
}

public static SearchQuery singleResult (String sortField, SortOrder sortOrder) {
@@ -126,36 +114,18 @@ public class SearchQuery {
return isInfinitePage() || pageSize > MAX_PAGE_BUFFER ? MAX_PAGE_BUFFER : pageSize;
}

@Setter private String sortField;
public String getSortField() {
if (empty(sortField)) return null;
if (sortField.contains(";")) die("invalid sort: "+sortField);

// only return the first several chars, to thwart a hypothetical injection attack
// more sophisticated than the classic 'add a semi-colon then do something nefarious'
final String sort = empty(sortField) ? DEFAULT_SORT_FIELD : sortField;
return StringUtil.prefix(sort, MAX_SORTFIELD_LENGTH);
}
@JsonIgnore public boolean getHasSortField () { return sortField != null; }

@ValidEnum(type=SortOrder.class, emptyOk=true, message= BasicConstraintConstants.ERR_SORT_ORDER_INVALID)
@Getter private String sortOrder = SearchQuery.DEFAULT_SORT;
public SearchQuery setSortOrder(Object thing) {
if (thing == null) {
sortOrder = null;
} else if (thing instanceof SortOrder) {
sortOrder = ((SortOrder) thing).name();
@Getter @Setter private SearchSort[] sorts;
@JsonIgnore public boolean hasSorts() { return !empty(sorts); }
public boolean hasSort(String field) { return !empty(sorts) && Arrays.stream(sorts).anyMatch(s -> s.getSortField().equals(field)); }
public SearchQuery addSort(SearchSort sort) {
if (sorts == null) {
sorts = new SearchSort[] {sort};
} else {
sortOrder = thing.toString();
sorts = ArrayUtil.append(sorts, sort);
}
return this;
}

@JsonIgnore public SortOrder getSortType () { return sortOrder == null ? null : SortOrder.valueOf(sortOrder); }

public SearchQuery sortAscending () { sortOrder = SortOrder.ASC.name(); return this; }
public SearchQuery sortDescending () { sortOrder = SortOrder.DESC.name(); return this; }

@Setter private String filter = null;
public String getFilter() {
// only return the first several chars, to thwart a hypothetical injection attack.
@@ -194,8 +164,7 @@ public class SearchQuery {
if (getPageSize() != that.getPageSize()) return false;
if (!Arrays.equals(that.bounds, bounds)) return false;
if (filter != null ? !filter.equals(that.filter) : that.filter != null) return false;
if (sortField != null ? !sortField.equals(that.sortField) : that.sortField != null) return false;
if (sortOrder != null ? !sortOrder.equals(that.sortOrder) : that.sortOrder != null) return false;
if (!Arrays.equals(that.sorts, sorts)) return false;
if (!Arrays.equals(that.fields, fields)) return false;
return true;
}
@@ -203,11 +172,20 @@ public class SearchQuery {
@Override public int hashCode() {
int result = getPageNumber();
result = 31 * result + getPageSize();
result = 31 * result + (sortField != null ? sortField.hashCode() : 0);
result = 31 * result + (sortOrder != null ? sortOrder.hashCode() : 0);
result = 31 * result + (hasSorts() ? Arrays.deepHashCode(this.sorts) : 0);
result = 31 * result + (filter != null ? filter.hashCode() : 0);
result = 31 * result + (bounds != null ? Arrays.hashCode(bounds) : 0);
result = 31 * result + (fields != null ? Arrays.hashCode(fields) : 0);
return result;
}

public String hsqlSortClause(String entityAlias) {
if (!hasSorts()) return null;
final StringBuilder b = new StringBuilder();
for (SearchSort s : sorts) {
if (b.length() > 0) b.append(", ");
b.append(entityAlias).append(".").append(s.getSortField()).append(" ").append(s.getSortOrder().name());
}
return b.toString();
}
}

+ 30
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchSort.java Datei anzeigen

@@ -0,0 +1,30 @@
package org.cobbzilla.wizard.model.search;

import lombok.*;

@NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(of={"sortField", "sortOrder"})
public class SearchSort {

@Getter @Setter private String sortField;
@Getter @Setter private SortOrder sortOrder = SortOrder.ASC;

public SearchSort(String sort) {
if (sort.startsWith("+") || sort.startsWith(" ")) {
sortField = sort.substring(1).trim();
sortOrder = SortOrder.ASC;
} else if (sort.startsWith("-")) {
sortField = sort.substring(1).trim();
sortOrder = SortOrder.DESC;
} else if (sort.endsWith("+") || sort.endsWith(" ")) {
sortField = sort.substring(0, sort.length()-1).trim();
sortOrder = SortOrder.ASC;
} else if (sort.endsWith("-")) {
sortField = sort.substring(0, sort.length()-1).trim();
sortOrder = SortOrder.DESC;
} else {
sortField = sort.trim();
sortOrder = SortOrder.ASC;
}
}

}

+ 16
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SortOrder.java Datei anzeigen

@@ -0,0 +1,16 @@
package org.cobbzilla.wizard.model.search;

import com.fasterxml.jackson.annotation.JsonCreator;

public enum SortOrder {

ASC, DESC;

public static final String DEFAULT_SORT = DESC.name();

@JsonCreator public static SortOrder fromString(String val) { return valueOf(val.toUpperCase()); }

public boolean isAscending () { return this == ASC; }
public boolean isDescending () { return this == DESC; }

}

+ 2
- 1
wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractDAO.java Datei anzeigen

@@ -303,7 +303,8 @@ public abstract class AbstractDAO<E extends Identifiable> implements DAO<E> {
.append(filterClause);

final String countQuery = "select count(*) " + qBuilder.toString();
final String query = qBuilder.append(" order by ").append(entityAlias).append(".").append(searchQuery.getSortField()).append(" ").append(searchQuery.getSortType().name()).toString();
if (searchQuery.hasSorts()) qBuilder.append(searchQuery.hsqlSortClause(entityAlias));
final String query = qBuilder.toString();

List<E> results = query(query, searchQuery, params, values);
final int totalCount = Integer.valueOf(""+query(countQuery, SearchQuery.INFINITE_PAGE, params, values).get(0));


+ 26
- 16
wizard-server/src/main/java/org/cobbzilla/wizard/dao/SqlViewSearchHelper.java Datei anzeigen

@@ -4,8 +4,10 @@ import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.util.jdbc.ResultSetBean;
import org.cobbzilla.util.reflect.ReflectionUtil;
import org.cobbzilla.util.string.StringUtil;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.search.SearchSort;
import org.cobbzilla.wizard.model.search.SqlViewField;
import org.cobbzilla.wizard.model.search.SqlViewSearchResult;
import org.cobbzilla.wizard.server.config.PgRestServerConfiguration;
@@ -23,7 +25,7 @@ import static org.cobbzilla.util.daemon.DaemonThreadFactory.fixedPool;
import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate;
import static org.cobbzilla.wizard.model.search.SearchQuery.DEFAULT_SORT;
import static org.cobbzilla.wizard.model.search.SortOrder.ASC;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;

@Slf4j
@@ -57,14 +59,19 @@ public class SqlViewSearchHelper {
}
}

final String sort;
final String sortedField;
if (searchQuery.getHasSortField()) {
sortedField = dao.getSortField(searchQuery.getSortField());
sort = sortedField + " " + searchQuery.getSortOrder();
final StringBuilder sort = new StringBuilder();
final List<String> sortedFields = new ArrayList<>();
if (searchQuery.hasSorts()) {
for (SearchSort s : searchQuery.getSorts()) {
final String sortField = dao.getSortField(s.getSortField());
sortedFields.add(sortField);
if (sort.length() > 0) sort.append(", ");
sort.append(sortField).append(" ").append(s.getSortOrder().name());
}
} else {
sort = dao.getDefaultSort();
sortedField = sort.split(" ")[0];
final String defaultSort = dao.getDefaultSort();
sort.append(defaultSort);
sortedFields.add(defaultSort.split("\\s+")[0]);
}

final String offset;
@@ -81,7 +88,7 @@ public class SqlViewSearchHelper {
}

final String query = "select " + dao.getSelectClause(searchQuery) + " " + sql.toString() + sortClause + limit + offset;
log.debug("search: SQL = "+query);
log.debug("search: SQL = "+query+" with params: "+StringUtil.toString(params));

Integer totalCount = null;
final ArrayList<E> thingsList = new ArrayList<>();
@@ -149,15 +156,18 @@ public class SqlViewSearchHelper {
});

// manually sort and apply offset + limit
final SqlViewField sqlViewField = Arrays.stream(fields).filter(a -> a.getName().equals(sortedField)).findFirst().orElse(null);
if (sqlViewField == null) return die("search: sort field not defined/mapped: "+sortedField);
for (int i=0; i<sortedFields.size(); i++) {
final String sortField = sortedFields.get(i);
final SqlViewField sqlViewField = Arrays.stream(fields).filter(a -> a.getName().equals(sortField)).findFirst().orElse(null);
if (sqlViewField == null) return die("search: sort field not defined/mapped: " + sortField);

final Comparator<E> comparator = (E o1, E o2) -> compareSelectedItems(o1, o2, sqlViewField);
final Comparator<E> comparator = (E o1, E o2) -> compareSelectedItems(o1, o2, sqlViewField);

if (!searchQuery.getSortOrder().equals(DEFAULT_SORT)) {
matched.sort(comparator);
} else {
matched.sort(comparator.reversed());
if (searchQuery.getSorts()[i].getSortOrder() == ASC) {
matched.sort(comparator);
} else {
matched.sort(comparator.reversed());
}
}

totalCount = matched.size();


+ 9
- 7
wizard-server/src/main/java/org/cobbzilla/wizard/ldap/LdapServiceBase.java Datei anzeigen

@@ -1,5 +1,6 @@
package org.cobbzilla.wizard.ldap;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.CommandLine;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.util.system.Command;
@@ -7,6 +8,7 @@ import org.cobbzilla.util.system.CommandResult;
import org.cobbzilla.util.system.CommandShell;
import org.cobbzilla.wizard.model.ldap.LdapBindException;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.search.SortOrder;
import org.cobbzilla.wizard.server.config.LdapConfiguration;

import java.util.Map;
@@ -15,6 +17,7 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.system.CommandShell.okResult;

@Slf4j
public abstract class LdapServiceBase implements LdapService {

private LdapConfiguration config() { return getConfiguration(); }
@@ -62,13 +65,12 @@ public abstract class LdapServiceBase implements LdapService {
command.addArgument("-b").addArgument(dn, false);
} else {
if (!empty(filter) || !empty(bounds)) command.addArgument(ldapFilter(base, filter, bounds));
if (page.getHasSortField()) {
final SearchQuery.SortOrder sortOrder = page.getSortType();
final String sort = page.getSortField();
if (sort != null) {
final String sortArg = ((sortOrder != null && sortOrder == SearchQuery.SortOrder.DESC) ? "-" : "");
command.addArgument("-E").addArgument("!sss=" + sortArg + sort);
}
if (page.hasSorts()) {
if (page.getSorts().length > 1) log.warn("ldapsearch: only one sort order is supported");
final SortOrder sortOrder = page.getSorts()[0].getSortOrder();
final String sort = page.getSorts()[0].getSortField();
final String sortArg = ((sortOrder != null && sortOrder == SortOrder.DESC) ? "-" : "");
command.addArgument("-E").addArgument("!sss=" + sortArg + sort);
}
}
final CommandResult result = exec(command);


Laden…
Abbrechen
Speichern