package org.springframework.roo.addon.layers.repository.jpa.addon.finder.parser; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.roo.classpath.details.FieldMetadata; /** * This class is based on OrderBySource.java class from Spring Data commons project. * * It has some little changes to be able to work properly on Spring Roo project * and make easy Spring Data query parser. * * Get more information about original class on: * * https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java * * Represents an order clause, which is set after {@literal OrderBy} token. * It expects the last part of the query to be given and supports order by several properties ending with its sorting {@link Direction}. * * @author Paula Navarro * @author Juan Carlos GarcĂ­a * @since 2.0 */ public class OrderBySource { private static final String BLOCK_SPLIT = "(?<=Asc|Desc)(?=\\p{Lu}|\\z)"; private static final Pattern DIRECTION_SPLIT = Pattern.compile("(.*?)(Asc|Desc)?$"); private static final String INVALID_ORDER_SYNTAX = "Invalid order syntax for part %s!"; private static final Set<String> DIRECTION_KEYWORDS = new HashSet<String>(Arrays.asList("Asc", "Desc")); private final List<Order> orders; private List<FieldMetadata> fields; private final PartTree currentPartTreeInstance; /** * Creates a new {@link OrderBySource} for the given clause, checking the property referenced exists on the given * entity properties. * * @param partTree PartTree instance where current OrderBySource will be defined * @param clause must not be {@literal null}. * @param fields entity properties must not be {@literal null}. */ public OrderBySource(PartTree partTree, String clause, List<FieldMetadata> fields) { Validate.notNull(partTree, "ERROR: PartTree instance is necessary to generate OrderBy"); Validate.notNull(clause, "ERROR: Clause can not be null"); Validate.notNull(fields, "ERROR: Entity properties can not be null"); this.currentPartTreeInstance = partTree; this.orders = new ArrayList<Order>(); this.fields = fields; // Extract order properties for (String part : clause.split(BLOCK_SPLIT, -1)) { Matcher matcher = DIRECTION_SPLIT.matcher(part); if (!matcher.find()) { throw new IllegalArgumentException(String.format(INVALID_ORDER_SYNTAX, part)); } String propertyString = matcher.group(1); String directionString = matcher.group(2); // No property, but only a direction keyword if (DIRECTION_KEYWORDS.contains(propertyString) && directionString == null) { throw new RuntimeException(String.format(INVALID_ORDER_SYNTAX, part)); } // Invalid direction if (directionString != null && Direction.fromString(directionString) == null) { throw new RuntimeException("Invalid direction " + directionString); } Direction direction = StringUtils.isNotBlank(directionString) ? Direction.fromString(directionString) : null; this.orders.add(new Order(direction, currentPartTreeInstance.extractValidProperty( propertyString, fields))); } } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "OrderBy" + StringUtils.join(orders, ""); } /** * Returns the different order clauses that can be build based on the current order clause defined. * Query is added as options prefix. * * @param query prefix to be added to the options * @return */ public List<String> getOptions(String query) { List<String> options = new ArrayList<String>(); // Get the last order expression Order lastOrder = orders.get(orders.size() - 1); // Check if it has a property to order by if (!lastOrder.hasProperty()) { for (FieldMetadata field : fields) { options.add(query.concat(StringUtils.capitalize(field.getFieldName().toString()))); } // Once an order expression is defined, order clause can end if no more properties are added if (orders.size() > 1) { options.add(query.concat("")); } } else { // Show order directions after the property options.add(query.concat(Direction.ASC.getKeyword())); options.add(query.concat(Direction.DESC.getKeyword())); // If property is a reference to other entity, related entity properties can be added List<FieldMetadata> fields = currentPartTreeInstance.getValidProperties(lastOrder.getProperty().getLeft().peek() .getFieldType()); if (fields != null) { for (FieldMetadata field : fields) { options.add(query.concat(StringUtils.capitalize(field.getFieldName().toString()))); } } } return options; } }