content: package jp.aegif.nemaki.cmis.aspect;
  
  import jp.aegif.nemaki.cmis.aspect.type.TypeManager;
  import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap;
  import jp.aegif.nemaki.util.PropertyManager;
  import jp.aegif.nemaki.util.constant.PropertyKey;
  import org.apache.chemistry.opencmis.commons.PropertyIds;
  import org.apache.chemistry.opencmis.commons.data.ObjectData;
  import org.apache.chemistry.opencmis.commons.data.PropertyData;
  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
  import org.apache.chemistry.opencmis.commons.enums.CapabilityOrderBy;
  import org.apache.chemistry.opencmis.commons.enums.PropertyType;
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.collections4.MapUtils;
  import org.apache.commons.collections4.comparators.ComparatorChain;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  import java.math.BigDecimal;
  import java.util.*;
  import java.util.Map.Entry;
  
  public class SortUtil {
  
  	private static final Log log = LogFactory.getLog(SortUtil.class);
  
  	private TypeManager typeManager;
  	private RepositoryInfoMap repositoryInfoMap;
  	private PropertyManager propertyManager;
  
  	@SuppressWarnings("unchecked")
  	public void sort(String repositoryId, List<ObjectData> list, String orderBy) {
  		//Check empty list
  		if(CollectionUtils.isEmpty(list)){
  			return;
  		}
  		
  		// Check orderBy argument
  		if (StringUtils.isEmpty(orderBy)) {
  			String defaultOrderBy = propertyManager
  					.readValue(PropertyKey.CAPABILITY_EXTENDED_ORDERBY_DEFAULT);
  			if (StringUtils.isBlank(defaultOrderBy)) {
  				return;
  			} else {
  				orderBy = defaultOrderBy;
  			}
  		}
  
  		// Check CapabilityOrderBy
  		CapabilityOrderBy capabilityOrderBy = repositoryInfoMap.get(repositoryId).getCapabilities().getOrderByCapability();
  		if (CapabilityOrderBy.NONE == capabilityOrderBy) {
  			return;
  		}
  
  		// Check the parsed result of orderBy argument
  		LinkedHashMap<PropertyDefinition<?>, Boolean> _orderBy = parseOrderBy(
  				orderBy, capabilityOrderBy);
  		if (MapUtils.isEmpty(_orderBy)) {
  			return;
  		}
  
  		// Build ComparatorChain
  		ComparatorChain chain = new ComparatorChain();
  		for (Entry<PropertyDefinition<?>, Boolean> o : _orderBy.entrySet()) {
  			PropertyComparator comparator = new PropertyComparator(o.getKey());
  			chain.addComparator(comparator, o.getValue());
  		}
  
  		// Sort
  		Collections.sort(list, chain);
  	}
  
  	private class PropertyComparator implements Comparator<ObjectData> {
  		PropertyDefinition<?> propertyDefinition;
  
  		public PropertyComparator(PropertyDefinition<?> propertyDefinition) {
  			this.propertyDefinition = propertyDefinition;
  		}
  
  		@Override
  		public int compare(ObjectData o1, ObjectData o2) {
  			String propertyId = propertyDefinition.getId();
  			PropertyData<?> pd1 = o1.getProperties().getProperties()
  					.get(propertyId);
  			PropertyData<?> pd2 = o2.getProperties().getProperties()
  					.get(propertyId);
  
  			Object val1 = null;
  			Object val2 = null;
  		
  			if(pd1 != null){
  				val1 = pd1.getFirstValue();
  			}
  			if(pd2 != null){
  				val2 = pd2.getFirstValue();
  			}
  			
  			// Null values are put to the last
  			if (val1 == null && val2 == null) {
  				return 0;
  			} else if (val1 == null && val2 != null) {
  				return 1;
  			} else if (val1 != null && val2 == null) {
  				return -1;
  			} else {
  				PropertyType pType = propertyDefinition.getPropertyType();
  				if (PropertyType.STRING == pType || PropertyType.ID == pType
  						|| PropertyType.HTML == pType
  						|| PropertyType.URI == pType) {
  					String _val1 = (String) val1;
  					String _val2 = (String) val2;
  					return _val1.compareTo(_val2);
  				} else if (PropertyType.BOOLEAN == pType) {
  					Boolean _val1 = (Boolean) val1;
  					Boolean _val2 = (Boolean) val2;
  					return _val1.compareTo(_val2);
  				} else if (PropertyType.DATETIME == pType) {
  					GregorianCalendar _val1 = (GregorianCalendar) val1;
  					GregorianCalendar _val2 = (GregorianCalendar) val2;
  					return _val1.compareTo(_val2);
  				} else if (PropertyType.INTEGER == pType) {
  					Integer _val1 = (Integer) val1;
  					Integer _val2 = (Integer) val2;
  					return _val1.compareTo(_val2);
  				} else if (PropertyType.DECIMAL == pType) {
  					BigDecimal _val1 = (BigDecimal) val1;
  					BigDecimal _val2 = (BigDecimal) val2;
  					return _val1.compareTo(_val2);
  				}
  			}
  
  			return 0;
  		}
  	}
  
  	/**
  	 * Build an ordered map of PropertyDefinition and reverse order flag
  	 * 
  	 * @param orderBy
  	 * @return
  	 */
  	private LinkedHashMap<PropertyDefinition<?>, Boolean> parseOrderBy(
  			String orderBy, CapabilityOrderBy capabilityOrderBy) {
  
  		if (StringUtils.isBlank(orderBy)) {
  			return null;
  		}
  
  		String[] orders = StringUtils.split(orderBy, ",");
  		LinkedHashMap<PropertyDefinition<?>, Boolean> map = new LinkedHashMap<PropertyDefinition<?>, Boolean>();
  		for (int i = 0; i < orders.length; i++) {
  			// Split queryName and its modifier(separated with space)
  			String[] order = orders[i].split("[\\s]+");
  
  			// Property definition
  			PropertyDefinition<?> pdf = convertToPropertyDefinition(order[0]);
  			if (pdf == null) {
  				log.warn("Invalid property query name in orderBy parameter is ignored: propertyId="
  						+ order[0]);
  				continue;
  			} else if (capabilityOrderBy == CapabilityOrderBy.COMMON &&
  					!isCommonProperty(pdf)) {
  				log.warn("This property query name in orderBy parameter is not supported when capabilityOrderBy=common: propertyId="
  						+ order[0]);
  				continue;
  			// "orderable" can be different even when the same property id
  			/*}else if(!pdf.isOrderable()){
  				log.warn("This property query name in orderBy parameter is not orderable and ignored: propertyId="
  						+ order[0]);*/
  			}
  
  			// Modifier
  			Boolean desc = false;
  			if (order.length > 1) {
  				if ("DESC".equals(order[1])) {
  					desc = true;
  				} else if (StringUtils.isNotBlank(order[1])) {
  					log.info("Invalid modifier other than DESC in orderBy parameter is ignored: propertyId="
  							+ order[0]);
  				}
  			}
  
  			map.put(pdf, desc);
  		}
  		return map;
  	}
  
  	/**
  	 * Get a PropertyDefinition cores from queryName
  	 * 
  	 * @param queryName
  	 * @return
  	 */
  	private PropertyDefinition<?> convertToPropertyDefinition(String queryName) {
  		String[] _queryName = StringUtils.split(queryName, ".");
  		if (_queryName.length == 1) {
  			return typeManager.getPropertyDefinitionCoreForQueryName(queryName);
  		} else {
  			return typeManager
  					.getPropertyDefinitionCoreForQueryName(_queryName[1]);
  		}
  	}
  
  	private boolean isCommonProperty(PropertyDefinition<?> propertyDefinition) {
  		String id = propertyDefinition.getId();
  
  		return PropertyIds.NAME.equals(id) || PropertyIds.OBJECT_ID.equals(id)
  				|| PropertyIds.OBJECT_TYPE_ID.equals(id)
  				|| PropertyIds.BASE_TYPE_ID.equals(id)
  				|| PropertyIds.CREATED_BY.equals(id)
  				|| PropertyIds.CREATION_DATE.equals(id)
  				|| PropertyIds.LAST_MODIFIED_BY.equals(id)
  				|| PropertyIds.LAST_MODIFICATION_DATE.equals(id)
  				|| PropertyIds.IS_IMMUTABLE.equals(id)
  				|| PropertyIds.IS_PRIVATE_WORKING_COPY.equals(id)
  				|| PropertyIds.IS_LATEST_VERSION.equals(id)
  				|| PropertyIds.IS_MAJOR_VERSION.equals(id)
  				|| PropertyIds.IS_LATEST_MAJOR_VERSION.equals(id)
  				|| PropertyIds.VERSION_LABEL.equals(id)
  				|| PropertyIds.VERSION_SERIES_ID.equals(id)
  				|| PropertyIds.IS_VERSION_SERIES_CHECKED_OUT.equals(id)
  				|| PropertyIds.VERSION_SERIES_CHECKED_OUT_BY.equals(id)
  				|| PropertyIds.VERSION_SERIES_CHECKED_OUT_ID.equals(id)
  				|| PropertyIds.CHECKIN_COMMENT.equals(id)
  				|| PropertyIds.CONTENT_STREAM_LENGTH.equals(id)
  				|| PropertyIds.CONTENT_STREAM_MIME_TYPE.equals(id)
  				|| PropertyIds.CONTENT_STREAM_FILE_NAME.equals(id)
  				|| PropertyIds.CONTENT_STREAM_ID.equals(id)
  				|| PropertyIds.PARENT_ID.equals(id)
  				|| PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS.equals(id)
  				|| PropertyIds.PATH.equals(id);
  	}
  
  	public void setTypeManager(TypeManager typeManager) {
  		this.typeManager = typeManager;
  	}
  	
  	public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) {
  		this.repositoryInfoMap = repositoryInfoMap;
  	}
  
  	public void setPropertyManager(PropertyManager propertyManager) {
  		this.propertyManager = propertyManager;
  	}
  }