001/** 002 * Copyright 2014 Tampere University of Technology, Pori Department 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package core.tut.pori.http.parameters; 017 018import java.util.HashMap; 019import java.util.Iterator; 020import java.util.LinkedHashMap; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.commons.lang3.StringUtils; 027import org.apache.log4j.Logger; 028 029import core.tut.pori.dao.SQLSelectBuilder.OrderDirection; 030import core.tut.pori.http.Definitions; 031 032 033/** 034 * <p>The default parser for sort options.</p> 035 * 036 * <p>The basic syntax is: ?PARAMETER=OrderDirection, e.g. ?sort=<</p> 037 * 038 * <p>It is also possible to give type specific order clauses, e.g. ?sort=TYPE1;<,TYPE2;></p> 039 * 040 * <p>Also, you can provide specific element name, e.g. ?sort=<ELEMENT_NAME or ?sort=TYPE1;>ELEMENT_NAME</p> 041 * 042 * <p>These to variations can be combined, e.g. ?sort=<,TYPE;> to provide different sort option for a specific type</p> 043 * 044 * <p>Note: the parses has no way of knowing if the given TYPE or ELEMENT_NAME value is valid, you should check the values yourself</p> 045 * 046 * <p>Any number of sort clauses (SortOption) can be given, and they will be processed in-order by the default SQL and SOLR query builders.</p> 047 */ 048public final class SortOptions extends HTTPParameter{ 049 /** String for ascending sort */ 050 public static final String ORDER_DIRECTION_ASCENDING = "<"; 051 /** String for descending sort */ 052 public static final String ORDER_DIRECTION_DESCENDING = ">"; 053 /** Recommended name for the sort parameter */ 054 public static final String PARAMETER_DEFAULT_NAME = "sort_by"; 055 private static final Logger LOGGER = Logger.getLogger(SortOptions.class); 056 private static final int ORDER_DIRECTION_ASCENDING_LENGTH = ORDER_DIRECTION_ASCENDING.length(); 057 private static final int ORDER_DIRECTION_DESCENDING_LENGTH = ORDER_DIRECTION_DESCENDING.length(); 058 private Map<String, Set<Option>> _typeOrderDirections = null; // type-name, sort option map 059 060 /** 061 * 062 * @param options 063 * @param typeName 064 * @return the sort options or null if none 065 */ 066 public static Set<Option> getSortOptions(SortOptions options, String typeName){ 067 if(options == null){ 068 return null; 069 }else{ 070 return options.getSortOptions(typeName); 071 } 072 } 073 074 /** 075 * @param typeName 076 * @return set of sort options, the iteration order of the map is predictable, i.e. the values are iterated in the order given by the user. Or, null if none provided by the user. 077 */ 078 public Set<Option> getSortOptions(String typeName){ 079 if(_typeOrderDirections == null){ 080 return null; 081 }else{ 082 Set<Option> o = _typeOrderDirections.get(typeName); 083 if(o == null){ 084 return getDefaultSortOptions(); 085 }else{ 086 return o; 087 } 088 } 089 } 090 091 /** 092 * 093 * @param option 094 */ 095 public void addSortOption(Option option){ 096 if(option == null){ 097 LOGGER.debug("Ignored null option."); 098 return; 099 } 100 Set<Option> options = null; 101 String typename = option.getTypeName(); 102 if(_typeOrderDirections == null){ 103 _typeOrderDirections = new HashMap<>(); 104 _typeOrderDirections.put(typename, (options = new LinkedHashSet<>())); 105 }else{ 106 options = _typeOrderDirections.get(typename); 107 if(options == null){ 108 _typeOrderDirections.put(typename, (options = new LinkedHashSet<>())); 109 } 110 } 111 options.add(option); 112 } 113 114 /** 115 * 116 * @return non-typed sort options, or null if not given by the user 117 */ 118 public Set<Option> getDefaultSortOptions(){ 119 return (_typeOrderDirections == null ? null : _typeOrderDirections.get(null)); 120 } 121 122 @Override 123 public void initialize(List<String> parameterValues) throws IllegalArgumentException { 124 for(Iterator<String> iter = parameterValues.iterator();iter.hasNext();){ 125 initializeSort(iter.next()); 126 } 127 } 128 129 @Override 130 public void initialize(String parameterValue) throws IllegalArgumentException { 131 if(StringUtils.isBlank(parameterValue)){ 132 _typeOrderDirections = null; 133 return; 134 } 135 initializeSort(parameterValue); 136 } 137 138 /** 139 * 140 * @param valueString 141 * @throws IllegalArgumentException on bad valueString 142 */ 143 private void initializeSort(String valueString) throws IllegalArgumentException{ 144 if(StringUtils.isBlank(valueString)){ 145 LOGGER.debug("Detected null or empty value for parameter: "+getParameterName()); 146 return; 147 } 148 String[] parts = StringUtils.split(valueString, Definitions.SEPARATOR_URI_QUERY_TYPE_VALUE); 149 Option option = null; 150 if(parts.length > 2){ // bad input 151 throw new IllegalArgumentException("Invalid value "+valueString+" for paramater "+getParameterName()); 152 }else if(parts.length == 2){ //TYPE;ELEMENT, TYPE;<ELEMENT, TYPE;>ELEMENT, TYPE;> or TYPE;< 153 option = parseOption(parts[1]); 154 option._typeName = parts[0]; 155 }else{ // <, >, <ELEMENT, >ELEMENT or ELEMENT 156 option = parseOption(parts[0]); 157 } 158 159 Set<Option> set = null; 160 if(_typeOrderDirections == null){ 161 _typeOrderDirections = new LinkedHashMap<>(); 162 }else{ 163 set = _typeOrderDirections.get(option._typeName); 164 } 165 166 if(set == null){ 167 set = new LinkedHashSet<>(); 168 _typeOrderDirections.put(option._typeName, set); 169 } 170 171 set.add(option); 172 } 173 174 /** 175 * helper method for parsin the sort string 176 * 177 * @param sortString ELEMENT, <ELEMENT, >ELEMENT, > or < 178 * @return the parsed option 179 * @throws IllegalArgumentException on bad input 180 */ 181 private Option parseOption(String sortString) throws IllegalArgumentException{ 182 if(StringUtils.isBlank(sortString)){ 183 throw new IllegalArgumentException("Invalid value "+sortString+" for paramater "+getParameterName()); 184 } 185 186 OrderDirection od = null; 187 String sortElementName = null; 188 if(sortString.startsWith(ORDER_DIRECTION_ASCENDING)){ 189 od = OrderDirection.ASCENDING; 190 if(sortString.length() > ORDER_DIRECTION_ASCENDING_LENGTH){ // <ELEMENT_NAME 191 sortElementName = sortString.substring(ORDER_DIRECTION_ASCENDING_LENGTH); 192 } // only < 193 }else if(sortString.startsWith(ORDER_DIRECTION_DESCENDING)){ 194 od = OrderDirection.DESCENDING; 195 if(sortString.length() > ORDER_DIRECTION_DESCENDING_LENGTH){ //> ELEMENT_NAME 196 sortElementName = sortString.substring(ORDER_DIRECTION_DESCENDING_LENGTH); 197 } // only > 198 }else{ // must be the element name 199 sortElementName = sortString; 200 } 201 return new Option(sortElementName,od, null); 202 } 203 204 /** 205 * Note: getValue() may return null even though this method returns null, if user has provided a specific 206 * sorting option, which is NOT a default option 207 * 208 * @return true if user has provided values, but not if only default value is given 209 */ 210 @Override 211 public boolean hasValues() { 212 return (_typeOrderDirections != null); 213 } 214 215 /** 216 * @return the general sort order 217 */ 218 @Override 219 public Set<Option> getValue() { 220 return getDefaultSortOptions(); 221 } 222 223 /** 224 * A single sort option. 225 */ 226 public static class Option{ 227 private String _elementName = null; 228 private OrderDirection _orderDirection = OrderDirection.DESCENDING; 229 private String _typeName = null; 230 231 /** 232 * 233 * @param elementName 234 * @param direction 235 * @param typeName 236 */ 237 public Option(String elementName, OrderDirection direction, String typeName){ 238 _elementName = elementName; 239 _typeName = typeName; 240 if(direction == null){ 241 LOGGER.debug("null "+OrderDirection.class.toString()+", using default: "+_orderDirection.name()); 242 }else{ 243 _orderDirection = direction; 244 } 245 } 246 247 /** 248 * @return the elementName 249 */ 250 public String getElementName() { 251 return _elementName; 252 } 253 254 /** 255 * @return the orderDirection 256 */ 257 public OrderDirection getOrderDirection() { 258 return _orderDirection; 259 } 260 261 /** 262 * @return the typeName 263 */ 264 public String getTypeName() { 265 return _typeName; 266 } 267 268 @Override 269 public int hashCode() { 270 final int prime = 31; 271 int result = 1; 272 result = prime * result + ((_elementName == null) ? 0 : _elementName.hashCode()); 273 result = prime * result + ((_orderDirection == null) ? 0 : _orderDirection.hashCode()); 274 result = prime * result + ((_typeName == null) ? 0 : _typeName.hashCode()); 275 return result; 276 } 277 278 @Override 279 public boolean equals(Object obj) { 280 if (this == obj) 281 return true; 282 if (obj == null) 283 return false; 284 if (getClass() != obj.getClass()) 285 return false; 286 Option other = (Option) obj; 287 if (_elementName == null) { 288 if (other._elementName != null) 289 return false; 290 } else if (!_elementName.equals(other._elementName)) 291 return false; 292 if (_orderDirection != other._orderDirection) 293 return false; 294 if (_typeName == null) { 295 if (other._typeName != null) 296 return false; 297 } else if (!_typeName.equals(other._typeName)) 298 return false; 299 return true; 300 } 301 } // class Option 302}