Ability to specify sorting properties for database sorting used by gui tables. #PL-5354

This commit is contained in:
Konstantin Krivopustov 2015-05-07 05:51:25 +00:00
parent 0720c9764e
commit ecc18a659b
8 changed files with 178 additions and 58 deletions

View File

@ -140,13 +140,13 @@ public class QueryTransformerRegexTest extends TestCase
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
"group by c.level having c.level > 0 order by c.level",
"sec$GroupHierarchy");
transformer.replaceOrderBy("group", false);
transformer.replaceOrderBy(false, "group");
String res = transformer.getResult();
assertEquals(
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
"group by c.level having c.level > 0 order by h.group",
res);
transformer.replaceOrderBy("group", true);
transformer.replaceOrderBy(true, "group");
res = transformer.getResult();
assertEquals(
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
@ -161,7 +161,7 @@ public class QueryTransformerRegexTest extends TestCase
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
"group by c.level having c.level > 0 order by h.group asc",
"sec$GroupHierarchy");
transformer.replaceOrderBy("group", true);
transformer.replaceOrderBy(true, "group");
String res = transformer.getResult();
assertEquals(
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
@ -172,7 +172,7 @@ public class QueryTransformerRegexTest extends TestCase
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
"group by c.level having c.level > 0 order by h.group desc",
"sec$GroupHierarchy");
transformer.replaceOrderBy("group", false);
transformer.replaceOrderBy(false, "group");
res = transformer.getResult();
assertEquals(
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
@ -183,7 +183,7 @@ public class QueryTransformerRegexTest extends TestCase
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
"group by c.level having c.level > 0 order by h.group desc",
"sec$GroupHierarchy");
transformer.replaceOrderBy("group", true);
transformer.replaceOrderBy(true, "group");
res = transformer.getResult();
assertEquals(
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
@ -196,12 +196,12 @@ public class QueryTransformerRegexTest extends TestCase
QueryTransformerRegex transformer = new QueryTransformerRegex(
"select c from ref$Car c where c.deleteTs is null",
"ref$Car");
transformer.replaceOrderBy("model.numberOfSeats", false);
transformer.replaceOrderBy(false, "model.numberOfSeats");
String res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c_model.numberOfSeats",
res);
transformer.replaceOrderBy("model.numberOfSeats", true);
transformer.replaceOrderBy(true, "model.numberOfSeats");
res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c_model.numberOfSeats desc",
@ -211,18 +211,49 @@ public class QueryTransformerRegexTest extends TestCase
transformer = new QueryTransformerRegex(
"select c from ref$Car c where c.deleteTs is null",
"ref$Car");
transformer.replaceOrderBy("model.manufacturer.name", false);
transformer.replaceOrderBy(false, "model.manufacturer.name");
res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model.manufacturer c_model_manufacturer where c.deleteTs is null order by c_model_manufacturer.name",
res);
transformer.replaceOrderBy("model.manufacturer.name", true);
transformer.replaceOrderBy(true, "model.manufacturer.name");
res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model.manufacturer c_model_manufacturer where c.deleteTs is null order by c_model_manufacturer.name desc",
res);
}
public void testOrderBySeveralProperties() throws Exception {
QueryTransformerRegex transformer = new QueryTransformerRegex(
"select c from ref$Car c where c.deleteTs is null",
"ref$Car");
transformer.replaceOrderBy(false, "createTs", "vin");
String res = transformer.getResult();
assertEquals(
"select c from ref$Car c where c.deleteTs is null order by c.createTs, c.vin",
res);
transformer.replaceOrderBy(false, "vin", "model.numberOfSeats");
res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c.vin, c_model.numberOfSeats",
res);
transformer.replaceOrderBy(true, "vin", "model.numberOfSeats");
res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c.vin desc, c_model.numberOfSeats desc",
res);
transformer.replaceOrderBy(false, "model.numberOfSeats", "vin");
res = transformer.getResult();
assertEquals(
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c_model.numberOfSeats, c.vin",
res);
}
public void testRemoveDistinct() {
QueryTransformerRegex transformer;
String res;

View File

@ -7,14 +7,17 @@ package com.haulmont.chile.core.model.utils;
import com.haulmont.chile.core.annotations.NamePattern;
import com.haulmont.chile.core.datatypes.impl.EnumClass;
import com.haulmont.chile.core.model.Instance;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.DevelopmentException;
import com.haulmont.cuba.core.global.IllegalEntityStateException;
import com.haulmont.cuba.core.global.Messages;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@ -31,7 +34,7 @@ import static com.haulmont.bali.util.Preconditions.checkNotNullArgument;
*/
public final class InstanceUtils {
private static final Pattern instanceNameSplitPattern = Pattern.compile("[,;]");
private static final Pattern INSTANCE_NAME_SPLIT_PATTERN = Pattern.compile("[,;]");
private InstanceUtils() {
}
@ -228,36 +231,26 @@ public final class InstanceUtils {
public static String getInstanceName(Instance instance) {
checkNotNullArgument(instance, "instance is null");
String pattern = (String) instance.getMetaClass().getAnnotations().get(NamePattern.class.getName());
if (StringUtils.isBlank(pattern)) {
NamePatternRec rec = parseNamePattern(instance.getMetaClass());
if (rec == null) {
return instance.toString();
} else {
int pos = pattern.indexOf("|");
if (pos < 0)
throw new IllegalArgumentException("Invalid name pattern: " + pattern);
String format = StringUtils.substring(pattern, 0, pos);
String trimmedFormat = format.trim();
if (trimmedFormat.startsWith("#")) {
if (rec.methodName != null) {
try {
Method method = instance.getClass().getMethod(trimmedFormat.substring(1));
Method method = instance.getClass().getMethod(rec.methodName);
Object result = method.invoke(instance);
return (String) result;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
throw new RuntimeException("Error getting instance name", e);
}
}
String fieldsStr = StringUtils.substring(pattern, pos + 1);
// lazy initialized messages, used only for enum values
Messages messages = null;
String[] fields = instanceNameSplitPattern.split(fieldsStr);
Object[] values = new Object[fields.length];
for (int i = 0; i < fields.length; i++) {
Object value = instance.getValue(fields[i]);
Object[] values = new Object[rec.fields.length];
for (int i = 0; i < rec.fields.length; i++) {
Object value = instance.getValue(rec.fields[i]);
if (value == null) {
values[i] = "";
} else if (value instanceof Instance) {
@ -273,7 +266,51 @@ public final class InstanceUtils {
}
}
return String.format(format, values);
return String.format(rec.format, values);
}
}
/**
* Parse a name pattern defined by {@link NamePattern} annotation.
* @param metaClass entity meta-class
* @return record containing the name pattern properties, or null if the @NamePattern is not defined for the meta-class
*/
@Nullable
public static NamePatternRec parseNamePattern(MetaClass metaClass) {
String pattern = (String) metaClass.getAnnotations().get(NamePattern.class.getName());
if (StringUtils.isBlank(pattern))
return null;
int pos = pattern.indexOf("|");
if (pos < 0)
throw new DevelopmentException("Invalid name pattern: " + pattern);
String format = StringUtils.substring(pattern, 0, pos);
String trimmedFormat = format.trim();
String methodName = trimmedFormat.startsWith("#") ? trimmedFormat.substring(1) : null;
String fieldsStr = StringUtils.substring(pattern, pos + 1);
String[] fields = INSTANCE_NAME_SPLIT_PATTERN.split(fieldsStr);
return new NamePatternRec(format, methodName, fields);
}
public static class NamePatternRec {
/**
* Name pattern string format
*/
public final String format;
/**
* Formatting method name or null
*/
public final String methodName;
/**
* Array of property names
*/
public final String[] fields;
public NamePatternRec(String format, String methodName, String[] fields) {
this.fields = fields;
this.format = format;
this.methodName = methodName;
}
}
}

View File

@ -534,7 +534,15 @@ public class MetadataTools {
public List<String> getRelatedProperties(Class<?> entityClass, String property) {
List<String> result = new ArrayList<>();
MetaClass metaClass = metadata.getClassNN(entityClass);
MetaProperty metaProperty = metaClass.getPropertyNN(property);
return getRelatedProperties(metaClass.getPropertyNN(property));
}
/**
* @return list of related properties defined in {@link com.haulmont.chile.core.annotations.MetaProperty#related()}
* or empty list
*/
public List<String> getRelatedProperties(MetaProperty metaProperty) {
List<String> result = new ArrayList<>();
String relatedProperties = (String) metaProperty.getAnnotations().get("relatedProperties");
if (relatedProperties != null) {
result.addAll(Arrays.asList(relatedProperties.split(",")));

View File

@ -44,8 +44,12 @@ public interface QueryTransformer {
*/
boolean removeDistinct();
/** DEPRECATED: use {@link #replaceOrderBy(boolean, String...)} */
@Deprecated
void replaceOrderBy(String property, boolean desc);
/** Adds or replaces 'order by' clause */
void replaceOrderBy(String property, boolean asc);
void replaceOrderBy(boolean desc, String... properties);
/** Removes 'order by' clause */
void removeOrderBy();

View File

@ -241,7 +241,7 @@ public class QueryTransformerRegex extends QueryParserRegex implements QueryTran
Matcher distinctMatcher = DISTINCT_PATTERN.matcher(buffer);
buffer.replace(0, entityMatcher.start(),
"select "+ (distinctMatcher.find() ? "distinct " : "") + alias + ".id ");
"select " + (distinctMatcher.find() ? "distinct " : "") + alias + ".id ");
Matcher orderMatcher = ORDER_BY_PATTERN.matcher(buffer);
if (orderMatcher.find()) {
@ -260,34 +260,41 @@ public class QueryTransformerRegex extends QueryParserRegex implements QueryTran
}
@Override
@Deprecated
public void replaceOrderBy(String property, boolean desc) {
replaceOrderBy(desc, property);
}
@Override
public void replaceOrderBy(boolean desc, String... properties) {
Matcher entityMatcher = FROM_ENTITY_PATTERN.matcher(buffer);
String alias = findAlias(entityMatcher);
int dotPos = property.lastIndexOf(".");
if (dotPos > -1) {
String path = property.substring(0, dotPos);
String joinedAlias = alias + "_" + path.replace(".", "_");
if (buffer.indexOf(" " + joinedAlias) == -1) {
String join = "left join " + alias + "." + path + " " + joinedAlias;
addJoinAsIs(join);
}
String orderBy = joinedAlias + "." + property.substring(dotPos + 1) + (desc ? " desc" : "");
Matcher matcher = ORDER_BY_PATTERN.matcher(buffer);
if (matcher.find()) {
buffer.replace(matcher.end(), buffer.length(), " " + orderBy);
} else {
buffer.append(" order by ").append(orderBy);
}
Matcher orderByMatcher = ORDER_BY_PATTERN.matcher(buffer);
if (orderByMatcher.find()) {
buffer.replace(orderByMatcher.end(), buffer.length(), "");
} else {
String orderBy = alias + "." + property + (desc ? " desc" : "");
Matcher matcher = ORDER_BY_PATTERN.matcher(buffer);
if (matcher.find()) {
buffer.replace(matcher.end(), buffer.length(), " " + orderBy);
buffer.append(" order by");
}
String separator = " ";
for (String property : properties) {
int dotPos = property.lastIndexOf(".");
if (dotPos > -1) {
String path = property.substring(0, dotPos);
String joinedAlias = alias + "_" + path.replace(".", "_");
if (buffer.indexOf(" " + joinedAlias) == -1) {
String join = "left join " + alias + "." + path + " " + joinedAlias;
addJoinAsIs(join);
}
String orderBy = joinedAlias + "." + property.substring(dotPos + 1) + (desc ? " desc" : "");
buffer.append(separator).append(orderBy);
} else {
buffer.append(" order by ").append(orderBy);
String orderBy = alias + "." + property + (desc ? " desc" : "");
buffer.append(separator).append(orderBy);
}
separator = ", ";
}
}

View File

@ -175,10 +175,17 @@ public class QueryTransformerAstBased implements QueryTransformer {
return false;
}
@Deprecated
@Override
public void replaceOrderBy(String newOrderingFieldPath, boolean desc) {
replaceOrderBy(desc, newOrderingFieldPath);
}
@Override
public void replaceOrderBy(boolean desc, String... properties) {
EntityReferenceInferer inferer = new EntityReferenceInferer(entityName);
EntityReference ref = inferer.infer(queryAnalyzer);
queryAnalyzer.replaceOrderBy(ref.addFieldPath(newOrderingFieldPath), desc);
queryAnalyzer.replaceOrderBy(ref.addFieldPath(properties[0]), desc);
}
@Override

View File

@ -557,11 +557,36 @@ public abstract class AbstractCollectionDatasource<T extends Entity<K>, K>
protected void setSortDirection(LoadContext.Query q) {
boolean asc = Sortable.Order.ASC.equals(sortInfos[0].getOrder());
MetaPropertyPath propertyPath = sortInfos[0].getPropertyPath();
// Sort on DB only if the property is not transient and it is not an entity. Sorting by entity in JPQL
// translates to order by entity's id in SQL, that makes no sense.
String[] sortProperties = null;
if (metadata.getTools().isPersistent(propertyPath) && !propertyPath.getMetaProperty().getRange().isClass()) {
// a scalar persistent attribute
sortProperties = new String[1];
sortProperties[0] = propertyPath.toString();
} else if (propertyPath.getMetaProperty().getRange().isClass()) {
// a reference attribute
MetaClass metaClass = propertyPath.getMetaProperty().getRange().asClass();
InstanceUtils.NamePatternRec rec = InstanceUtils.parseNamePattern(metaClass);
if (rec != null) {
sortProperties = new String[rec.fields.length];
for (int i = 0; i < rec.fields.length; i++) {
sortProperties[i] = propertyPath.toString() + "." + rec.fields[i];
}
} else {
sortProperties = new String[1];
sortProperties[0] = propertyPath.toString();
}
} else if (!metadata.getTools().isPersistent(propertyPath)) {
// a non-persistent attribute
List<String> list = metadata.getTools().getRelatedProperties(propertyPath.getMetaProperty());
if (!list.isEmpty()) {
sortProperties = list.toArray(new String[list.size()]);
}
}
if (sortProperties != null) {
QueryTransformer transformer = QueryTransformerFactory.createTransformer(q.getQueryString(), metaClass.getName());
transformer.replaceOrderBy(propertyPath.toString(), !asc);
transformer.replaceOrderBy(!asc, sortProperties);
String jpqlQuery = transformer.getResult();
q.setQueryString(jpqlQuery);
}

View File

@ -725,7 +725,8 @@ public class CollectionPropertyDatasourceImpl<T extends Entity<K>, K>
public K firstItemId() {
Collection<T> collection = __getCollection();
if (collection != null && !collection.isEmpty()) {
return Iterables.getFirst(collection, null).getId();
T first = Iterables.getFirst(collection, null);
return first == null ? null : first.getId();
}
return null;
}