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.io.IOException; 019import java.io.InputStream; 020import java.io.UnsupportedEncodingException; 021import java.net.URLDecoder; 022import java.util.Iterator; 023import java.util.LinkedHashMap; 024import java.util.LinkedHashSet; 025import java.util.List; 026import java.util.Set; 027 028import org.apache.commons.io.IOUtils; 029import org.apache.commons.lang3.StringUtils; 030import org.apache.log4j.Logger; 031 032import core.tut.pori.http.Definitions; 033 034/** 035 * The default parser for query parameters, 036 * the syntax being: ?query=VALUE 037 * 038 * It is also possible to provide query for a specific type with ?query=TYPE:VALUE 039 * 040 * The two variations can be combined: ?query=TYPE;VALUE,VALUE 041 * 042 * The order of parameter values is preserved. Note that in all cases the order cannot be strictly preserved. 043 * For example, ?query=VALUE,TYPE:VALUE2,VALUE3, in this case the non-typed VALUE and VALUE3 will be grouped, and thus, 044 * both terms will appear before TYPE:VALUE2. 045 */ 046public class QueryParameter extends HTTPParameter{ 047 /** Recommended name for the Query parameter */ 048 public static final String PARAMETER_DEFAULT_NAME = "query"; 049 private static final Logger LOGGER = Logger.getLogger(QueryParameter.class); 050 private LinkedHashMap<String, Set<String>> _typeValueMap = null; // map of types and their values, null key is the default, non-typed value set 051 052 @Override 053 public void initializeRaw(String parameterValue) throws IllegalArgumentException { 054 parse(parameterValue); 055 } 056 057 @Override 058 public void initializeRaw(List<String> parameterValues) throws IllegalArgumentException { 059 for(Iterator<String> iter = parameterValues.iterator();iter.hasNext();){ 060 parse(iter.next()); 061 } 062 } 063 064 /** 065 * @param parameterValues list of URL decoded values, this will assume the whole string to be a single search term, WITHOUT type 066 */ 067 @Override 068 public void initialize(List<String> parameterValues) throws IllegalArgumentException { 069 for(Iterator<String> iter = parameterValues.iterator();iter.hasNext();){ 070 initialize(iter.next()); 071 } 072 } 073 074 /** 075 * @param parameterValue an URL decoded value, this will assume the whole string to be a single search term, WITHOUT type 076 */ 077 @Override 078 public void initialize(String parameterValue) throws IllegalArgumentException { 079 addQueryParameter(null, parameterValue); 080 } 081 082 /** 083 * 084 */ 085 public QueryParameter(){ 086 super(); 087 } 088 089 /** 090 * 091 * @param queryString URL decoded query string 092 */ 093 public QueryParameter(String queryString) { 094 if(queryString == null){ 095 LOGGER.debug("Null query string."); 096 }else{ 097 initialize(queryString); 098 } 099 } 100 101 /** 102 * 103 * @param parameterValue in URL decoded form 104 * @throws IllegalArgumentException on bad input 105 */ 106 private void parse(String parameterValue) throws IllegalArgumentException { 107 if(StringUtils.isBlank(parameterValue)){ 108 LOGGER.debug("Detected null or empty value for parameter: "+getParameterName()); 109 return; 110 } 111 String[] values = StringUtils.split(parameterValue, Definitions.SEPARATOR_URI_QUERY_TYPE_VALUE); 112 try{ 113 if(values.length == 1){ // only value 114 addQueryParameter(null, URLDecoder.decode(values[0], Definitions.ENCODING_UTF8)); 115 }else if(values.length == 2){ // type;value 116 addQueryParameter( URLDecoder.decode(values[0], Definitions.ENCODING_UTF8), URLDecoder.decode(values[1], Definitions.ENCODING_UTF8)); 117 }else{ // ;;;; or something 118 throw new IllegalArgumentException("Invalid value "+parameterValue+" for paramater "+getParameterName()); 119 } 120 } catch (UnsupportedEncodingException ex){ // this should never happen 121 LOGGER.error(ex, ex); 122 throw new IllegalArgumentException("Failed to decode "+parameterValue); // but if it does happen, abort here 123 } 124 } 125 126 /** 127 * 128 * @param type type of the query term 129 * @param value the query term 130 */ 131 public void addQueryParameter(String type, String value){ 132 if(_typeValueMap == null){ 133 _typeValueMap = new LinkedHashMap<>(); 134 } 135 Set<String> terms = _typeValueMap.get(type); 136 if(terms == null){ 137 terms = new LinkedHashSet<>(); 138 _typeValueMap.put(type, terms); 139 } 140 terms.add(value); 141 } 142 143 @Override 144 public boolean hasValues() { 145 return (_typeValueMap != null); 146 } 147 148 /** 149 * @return the non-typed search string, or null if none available 150 */ 151 @Override 152 public Set<String> getValue() { 153 return getValues(null); 154 } 155 156 /** 157 * 158 * @param typeName 159 * @return parameter values (queries) 160 */ 161 protected Set<String> getValues(String typeName){ 162 return (hasValues() ? _typeValueMap.get(typeName) : null); 163 } 164 165 /** 166 * 167 * @param param 168 * @param typeName 169 * @return set for the type name or null if none 170 */ 171 public static Set<String> getValues(QueryParameter param, String typeName){ 172 if(param == null){ 173 return null; 174 }else{ 175 return param.getValues(typeName); 176 } 177 } 178 179 @Override 180 public void initialize(InputStream parameterValue) throws IllegalArgumentException { 181 try { 182 initialize(IOUtils.toString(parameterValue)); 183 } catch (IOException ex) { 184 LOGGER.error(ex, ex); 185 throw new IllegalArgumentException("Failed to read HTTP body."); 186 } 187 } 188}