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.List; 021import java.util.Map; 022import java.util.Map.Entry; 023 024import org.apache.commons.lang3.StringUtils; 025import org.apache.log4j.Logger; 026 027import core.tut.pori.http.Definitions; 028 029 030/** 031 * The default parser for limit parameters, 032 * the syntax being: ?limits=START_ITEM-END_ITEM with a possible open ended limit for END_ITEM, e.g. ?limits=START_ITEM in this case the END_ITEM is automatically assigned to START_ITEM+DEFAULT_MAX_ITEMS-1 033 * 034 * It is also possible to provide limits for a specific type with ?limits=TYPE;START_ITEM-END_ITEM or for types with ?limits=TYPE;START_ITEM-END_ITEM,TYPE;START_ITEM-END_ITEM, 035 * the type limits can be retrieved using the parameterized getters, if no limits were provided for the type, default limits will be returned 036 * 037 * It is also possible to provide both the default limit and type specific limits: ?limits=START_ITEM-END_ITEM,TYPE;START_ITEM-END_ITEM 038 * 039 * Providing only the - character as a limit should as ?limits=- will be assumed to mean max items = 0, i.e. return nothing, this can also be used with typed limits, e.g. ?limits=0-1,TYPE;- to return no result for a specific type. 040 * 041 * The limits have no specific processing order, and can also be given in any order in the limits clause. The given order is generally internally preserved, though conceptually, no such order exists. 042 */ 043public final class Limits extends HTTPParameter{ 044 /** the default HTTP parameter name */ 045 public static final String PARAMETER_DEFAULT_NAME = "limits"; 046 /** The default maximum amount of items if no last item is specified */ 047 public static final int DEFAULT_MAX_ITEMS = Integer.MAX_VALUE; 048 private static final Logger LOGGER = Logger.getLogger(Limits.class); 049 private static final char SEPARATOR_LIMITS = '-'; 050 private Map<String, TypeLimits> _typeLimits = new HashMap<>(); // typeName - typeLimits map 051 052 /** 053 * Initialize limits 054 * 055 * @param startItem if < 0, 0 will be used 056 * @param endItem the last item index (inclusively), if <= startItem, startItem+DEFAULT_MAX_ITEM-1 will be used 057 */ 058 public Limits(int startItem, int endItem){ 059 _typeLimits.put(null, new TypeLimits(startItem, endItem, null)); 060 } 061 062 /** 063 * create default limits 064 */ 065 public Limits(){ 066 _typeLimits.put(null, new TypeLimits(0, DEFAULT_MAX_ITEMS, null)); 067 } 068 069 /** 070 * 071 * @return the limits as a limits string (query parameter value, without the parameter name and equals sign), or null if no limits 072 */ 073 public String toLimitString(){ 074 if(hasValues()){ 075 StringBuilder value = new StringBuilder(); 076 for(Iterator<Entry<String, TypeLimits>> iter = _typeLimits.entrySet().iterator();;){ 077 Entry<String, TypeLimits> e = iter.next(); 078 String type = e.getKey(); 079 if(type != null){ 080 value.append(type); 081 value.append(Definitions.SEPARATOR_URI_QUERY_TYPE_VALUE); 082 } 083 TypeLimits limits = e.getValue(); 084 value.append(limits.getStartItem()); 085 value.append(SEPARATOR_LIMITS); 086 value.append(limits.getEndItem()); 087 if(iter.hasNext()){ 088 value.append(Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 089 }else{ 090 break; 091 } 092 } 093 return value.toString(); 094 }else{ 095 return null; 096 } 097 } 098 099 /** 100 * 101 * @param startItem 102 * @param endItem 103 * @param typeName setting to null will replace the default (global) limits 104 */ 105 public void setTypeLimits(int startItem, int endItem, String typeName){ 106 _typeLimits.put(typeName, new TypeLimits(startItem, endItem, typeName)); 107 } 108 109 /** 110 * 111 * @return start item index 112 */ 113 public int getStartItem(){ 114 return _typeLimits.get(null).getStartItem(); 115 } 116 117 /** 118 * @return the endItem index 119 */ 120 public int getEndItem() { 121 return _typeLimits.get(null).getEndItem(); 122 } 123 124 /** 125 * 126 * @return maximum number of items 127 */ 128 public int getMaxItems(){ 129 return _typeLimits.get(null).getMaxItems(); // add one to return at least one result 130 } 131 132 /** 133 * 134 * @param typeName 135 * @return start item index for the given type or if the type does not exist, the general start item 136 */ 137 public int getStartItem(String typeName){ 138 if(_typeLimits == null){ 139 return getStartItem(); 140 } 141 TypeLimits l = _typeLimits.get(typeName); 142 if(l == null){ 143 return getStartItem(); 144 }else{ 145 return l.getStartItem(); 146 } 147 } 148 149 /** 150 * 151 * @param typeName 152 * @return the end item index or if the type is not defined, the general end item 153 */ 154 public int getEndItem(String typeName){ 155 if(_typeLimits == null){ 156 return getEndItem(); 157 } 158 TypeLimits l = _typeLimits.get(typeName); 159 if(l == null){ 160 return getEndItem(); 161 }else{ 162 return l.getEndItem(); 163 } 164 } 165 166 /** 167 * Note: if no limits for the given typeName is found, this will return the default limits. 168 * Whether default limits were returned or not can be checked by callint TypeLimits.getTypeName(), 169 * which will return null on default limits. 170 * 171 * @param typeName 172 * @return type limits for the requested type 173 */ 174 public TypeLimits getTypeLimits(String typeName){ 175 TypeLimits tl = _typeLimits.get(typeName); 176 if(tl == null){ 177 return _typeLimits.get(null); // return the default limits 178 }else{ 179 return tl; 180 } 181 } 182 183 /** 184 * 185 * @param typeName 186 * @return maximum number of items for the given type or maximum number of items in general if the given type is not defined 187 */ 188 public int getMaxItems(String typeName){ 189 if(_typeLimits == null){ 190 return getMaxItems(); 191 } 192 TypeLimits l = _typeLimits.get(typeName); 193 if(l == null){ 194 LOGGER.debug("Using defaults: No limits found for type: "+typeName); 195 return getMaxItems(); 196 }else{ 197 return l.getMaxItems(); // add one to return at least one result 198 } 199 } 200 201 @Override 202 public void initialize(List<String> parameterValues) throws IllegalArgumentException { 203 for(Iterator<String> iter = parameterValues.iterator();iter.hasNext();){ 204 initializeLimits(iter.next()); 205 } 206 } 207 208 @Override 209 public void initialize(String parameterValue) throws IllegalArgumentException { 210 initializeLimits(parameterValue); 211 } 212 213 /** 214 * 215 * @param valueString 0, 0- or 0-1 216 * @return limits parsed from the given string 217 * @throws IllegalArgumentException on bad value string 218 */ 219 private TypeLimits parseLimitValues(String valueString) throws IllegalArgumentException{ 220 if(StringUtils.isBlank(valueString)){ 221 LOGGER.debug("Detected null or empty value for parameter: "+getParameterName()); 222 return null; 223 } 224 String[] parts = StringUtils.split(valueString, SEPARATOR_LIMITS); 225 try{ 226 if(parts == null || parts.length > 2){ // probably limits=0-1-2-3 or similar 227 throw new IllegalArgumentException("Invalid "+getParameterName()+": "+valueString); 228 }else if(parts.length < 1){ // limits=- or similar 229 return new TypeLimits(-1, -1, null); 230 }else if(parts.length == 2){ // 0-1 231 return new TypeLimits(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), null); 232 }else{ // 0 (or 0-) 233 int item = Integer.parseInt(parts[0]); 234 return new TypeLimits(item, item, null); 235 } 236 }catch(NumberFormatException ex){ 237 LOGGER.debug(ex, ex); 238 throw new IllegalArgumentException("Invalid "+getParameterName()+": "+valueString); 239 } 240 } 241 242 /** 243 * 244 * @param limitString 245 * @throws IllegalArgumentException on bad input 246 */ 247 private void initializeLimits(String limitString) throws IllegalArgumentException{ 248 if(StringUtils.isBlank(limitString)){ 249 LOGGER.debug("Detected null or empty value for parameter: "+getParameterName()); 250 return; 251 } 252 String[] parts = StringUtils.split(limitString,Definitions.SEPARATOR_URI_QUERY_TYPE_VALUE); 253 if(parts.length == 1){ // 0, 0- or 0-1 254 TypeLimits limits = parseLimitValues(parts[0]); 255 _typeLimits.put(null, limits); 256 }else if(parts.length == 2){ // TYPE;0, TYPE;0- OR TYPE;0-1 257 TypeLimits limits = parseLimitValues(parts[1]); 258 limits._typeName = parts[0]; 259 _typeLimits.put(parts[0], limits); 260 }else{ 261 throw new IllegalArgumentException("Invalid "+getParameterName()+": "+limitString); 262 } 263 } 264 265 /** 266 * always true 267 */ 268 @Override 269 public boolean hasValues() { 270 return true; 271 } 272 273 /** 274 * @return there is no single value available, this always returns null 275 */ 276 @Override 277 public Object getValue() { 278 return null; 279 } 280 281 /** 282 * A type specific limit clause. 283 */ 284 public class TypeLimits{ 285 private int _startItem = -1; 286 private int _endItem = -1; 287 private String _typeName = null; 288 289 /** 290 * Create new TypeLimits. The limit validity will be checked: 291 * <ul> 292 * <li>If end item is smaller than start item, this will throw an exception</li> 293 * <li>if start item is less than 0, it will be set to 0</li> 294 * <li>if end item is INTEGER MAX, it will be set to INTEGER_MAX - 1. This is because the end item is included in the limits, which would cause interval START - END cause limit overflow (INTEGER MAX + 1)</li> 295 * <li>if end item is < 0, the maximum item count is assumed to be 0 and getMaxItems() will return 0 regardless of the given start item</li> 296 * </ul> 297 * @param startItem 298 * @param endItem 299 * @param typeName 300 * @throws IllegalArgumentException 301 */ 302 public TypeLimits(int startItem, int endItem, String typeName) throws IllegalArgumentException{ 303 _typeName = typeName; 304 if(startItem < 0){ 305 LOGGER.debug("Start item < 0, setting value to 0."); 306 _startItem = 0; 307 }else{ 308 _startItem = startItem; 309 } 310 if(endItem == Integer.MAX_VALUE){ 311 LOGGER.debug("End item = "+Integer.MAX_VALUE+" setting value to MAX-1."); 312 _endItem = endItem-1; 313 }else if(endItem < _startItem && endItem >= 0){ 314 throw new IllegalArgumentException("End item < start item."); 315 }else{ 316 _endItem = endItem; 317 } 318 } 319 320 /** 321 * @return the startItem 322 */ 323 public int getStartItem() { 324 return _startItem; 325 } 326 327 /** 328 * @return the endItem 329 */ 330 public int getEndItem() { 331 return _endItem; 332 } 333 334 /** 335 * @return the typeName 336 */ 337 public String getTypeName() { 338 return _typeName; 339 } 340 341 /** 342 * 343 * @return max item count 344 */ 345 public int getMaxItems() { 346 if(_endItem < 0){ 347 return 0; 348 } 349 return _endItem-_startItem+1; 350 } 351 } // class TypeLimits 352}