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}