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.Arrays;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map.Entry;
024import java.util.Set;
025
026import org.apache.commons.lang3.StringUtils;
027import org.apache.log4j.Logger;
028
029import core.tut.pori.http.Definitions;
030
031/**
032 * Default dataGroups parser.
033 * 
034 * Syntax: ?data_groups=DATA_GROUP_VALUE or with type ?data_groups=TYPE;DATA_GROUP_VALUE, these can be combined: ?data_groups=DATA_GROUP_VALUE,TYPE:DATA_GROUP_VALUE
035 * 
036 * Note: this has no way of knowing if the given string are valid for the required context, you should check the values manually
037 *
038 * There is no concept of order for the data groups, and they can be provided in any order for the data_groups parameter.
039 */
040public final class DataGroups extends HTTPParameter {
041  /** If present in a request, all available content should be returned in the response */
042  public static final String DATA_GROUP_ALL = "all";
043  /** If present in a request, the basic should be returned in the response. Note that the definition of "basic" is service specific. */
044  public static final String DATA_GROUP_BASIC = "basic";
045  /** If present in a request, the default content should be returned in the response. This in general equals to not providing a data group parameter. */
046  public static final String DATA_GROUP_DEFAULTS = "defaults";
047  /** the default HTTP parameter name */
048  public static final String PARAMETER_DEFAULT_NAME = "data_groups";
049  private static final Logger LOGGER = Logger.getLogger(DataGroups.class);
050  private HashMap<String, Set<String>> _dataGroups = null;  // type-datagroup map, null = default, non-typed datagroups
051
052  @Override
053  public void initialize(List<String> parameterValues) throws IllegalArgumentException {
054    for(Iterator<String> iter = parameterValues.iterator();iter.hasNext();){
055      parse(iter.next());
056    }
057  }
058
059  @Override
060  public void initialize(String parameterValue) throws IllegalArgumentException{
061    parse(parameterValue);
062  }
063  
064  /**
065   * 
066   * @param parameterValue
067   */
068  private void parse(String parameterValue){
069    if(StringUtils.isBlank(parameterValue)){
070      LOGGER.debug("Detected null or empty value for parameter: "+getParameterName());
071      return;
072    }
073    
074    String[] parts = StringUtils.split(parameterValue, Definitions.SEPARATOR_URI_QUERY_TYPE_VALUE);
075    if(parts.length == 2){  // type;value
076      addDataGroup(parts[1], parts[0]);
077    }else if(parts.length == 1){  // only value
078      addDataGroup(parts[0]);
079    }else{
080      throw new IllegalArgumentException("Invalid value "+parameterValue+" for paramater "+getParameterName());
081    }
082  }
083
084  @Override
085  public boolean hasValues() {
086    return (_dataGroups != null);
087  }
088  
089  /**
090   * varargs constructor for data groups
091   * 
092   * @param dataGroups
093   */
094  public DataGroups(String ...dataGroups) {
095    initialize(Arrays.asList(dataGroups));
096  }
097  
098  /**
099   * required for serialization
100   */
101  public DataGroups(){
102    // nothing needed
103  }
104  
105  /**
106   * Create a new copy based on the given dataGroup, this will create a deep copy of the given object.
107   * 
108   * @param dataGroups not-null
109   */
110  public DataGroups(DataGroups dataGroups){
111    if(dataGroups._dataGroups != null){
112      _dataGroups = new HashMap<>(dataGroups._dataGroups.size());
113      for(Entry<String,Set<String>> e : dataGroups._dataGroups.entrySet()){
114        _dataGroups.put(e.getKey(), new HashSet<>(e.getValue()));
115      }
116    }
117  }
118
119  /**
120   * for sub-classing, use the static
121   * 
122   * @param dataGroup
123   * @param type
124   * @return true if the given data group is given for the given type. If the type is not found, returns true if the data group is given by default (non-typed).
125   */
126  protected boolean hasDataGroup(String dataGroup, String type){
127    if(_dataGroups == null){
128      LOGGER.debug("No datagroups.");
129      return false;
130    }
131    Set<String> dataGroups = _dataGroups.get(type);
132    if(dataGroups == null && type != null){ // did not find for the type, try if there are defaults
133      dataGroups = _dataGroups.get(null);
134    }
135    return (dataGroups == null ? false : dataGroups.contains(dataGroup));
136  }
137
138  /**
139   * for sub-classing, use the static
140   * 
141   * @return true if this group contains no data groups
142   */
143  protected boolean isEmpty(){
144    return (_dataGroups == null ? true : _dataGroups.isEmpty());
145  }
146
147  /**
148   * 
149   * @param dataGroup
150   * @param dataGroups can be null
151   * @return true if the data group contains the given group
152   */
153  public static boolean hasDataGroup(String dataGroup, DataGroups dataGroups){
154    if(dataGroups == null){
155      return false;
156    }else{
157      return dataGroups.hasDataGroup(dataGroup, (String)null);
158    }
159  }
160  
161  /**
162   * 
163   * @param dataGroup
164   * @param dataGroups can be null
165   * @param type
166   * @return true if the given data group is given for the given type and data group. If the type is not found, returns true if the data group is given by default (non-typed).
167   */
168  public static boolean hasDataGroup(String dataGroup, DataGroups dataGroups, String type){
169    if(dataGroups == null){
170      return false;
171    }else{
172      return dataGroups.hasDataGroup(dataGroup, type);
173    }
174  }
175
176  /**
177   * Remove the data group from the default type
178   * 
179   * @param dataGroup
180   * @return true if the dataGroup was removed
181   */
182  public boolean removeDataGroup(String dataGroup) {
183    return removeDataGroup(dataGroup, null);
184  }
185  
186  /**
187   * Remove data group from the given type
188   * 
189   * @param dataGroup
190   * @param type
191   * @return true if the group was removed (existed in this group)
192   */
193  public boolean removeDataGroup(String dataGroup, String type){
194    if(_dataGroups == null){
195      return false;
196    }
197    Set<String> dataGroups = _dataGroups.get(type);
198    if(dataGroups == null){
199      return false;
200    }
201    return dataGroups.remove(dataGroup);
202  }
203
204  /**
205   * Add datagroup to the default, non-typed datagroup list
206   * 
207   * @param dataGroup
208   */
209  public void addDataGroup(String dataGroup) {
210    addDataGroup(dataGroup, null);
211  }
212  
213  /**
214   * Add the given dataGroup for the given type
215   * 
216   * @param dataGroup
217   * @param type
218   */
219  public void addDataGroup(String dataGroup, String type) {
220    if(_dataGroups == null){
221      _dataGroups = new HashMap<>();
222    }
223    Set<String> dataGroups = _dataGroups.get(type);
224    if(dataGroups == null){
225      dataGroups = new HashSet<>();
226      _dataGroups.put(type, dataGroups);
227    }
228    dataGroups.add(dataGroup);
229  }
230  
231  /**
232   * 
233   * @param dataGroups
234   * @return true if this group is empty
235   */
236  public static boolean isEmpty(DataGroups dataGroups){
237    if(dataGroups == null){
238      return true;
239    }else{
240      return !dataGroups.hasValues();
241    }
242  }
243  
244  /**
245   * 
246   * @return this group as data group uri string
247   */
248  public String toDataGroupString(){
249    if(!hasValues()){
250      return null;
251    }
252    StringBuilder sb = new StringBuilder();
253    for(Iterator<Entry<String, Set<String>>> iter = _dataGroups.entrySet().iterator();;){
254      Entry<String, Set<String>> e = iter.next();
255      String type = e.getKey();
256      Iterator<String> vIter = e.getValue().iterator();
257      if(type != null){ // there is a type, we must separate all values to diffrent parts: TYPE:VALUE,TYPE:VALUE,TYPE:VALUE
258        do{
259          sb.append(type);
260          sb.append(Definitions.SEPARATOR_URI_QUERY_TYPE_VALUE);
261          sb.append(vIter.next());  // there should always be at least one data group per type  
262        }while(vIter.hasNext());
263      }else{  // no type, we can just add the values: VALUE,VALUE,VALUE
264        sb.append(vIter.next());  // there should always be at least one data group per type
265        while(vIter.hasNext()){
266          sb.append(Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES);
267          sb.append(vIter.next());
268        }
269      }
270      
271      if(iter.hasNext()){
272        sb.append(Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES);
273      }else{
274        break;
275      }
276    }
277    
278    return sb.toString();
279  }
280
281  /**
282   * @return the datagroups as datagroup String or null if no datagroups
283   */
284  @Override
285  public String getValue() {
286    return toDataGroupString();
287  }
288}