package com.temenos.interaction.core.rim;
/*
* #%L
* interaction-core
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.ResponseBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* This class contains a number of utility methods to manipulate
* HTTP response headers via a system of ResponseBuilders.
*
* @author dgroves
*
*/
public class HeaderHelper {
private static final Logger logger = LoggerFactory.getLogger(HeaderHelper.class);
private static final String DEFAULT_ENCODING = "UTF-8";
/**
* Add an HTTP Allow header to the response.
* @param rb
* @param httpMethods
* @return
*/
public static ResponseBuilder allowHeader(ResponseBuilder rb, Set<String> httpMethods) {
if (httpMethods != null) {
StringBuilder result = new StringBuilder();
for (String method : httpMethods) {
result.append(" ");
result.append(method);
result.append(",");
}
return rb.header("Allow", (result.length() > 0 ? result.substring(1, result.length() - 1) : ""));
}
return rb;
}
/**
* Add an HTTP Location header to the response without query parameters.
* @param rb
* @param target
* @return
*/
public static ResponseBuilder locationHeader(ResponseBuilder rb, String target) {
return locationHeader(rb, target, null);
}
/**
* Add an HTTP Location header to the response with query parameters.
* @param rb
* @param target
* @param queryParam
* @return
*/
public static ResponseBuilder locationHeader(ResponseBuilder rb, String target,
MultivaluedMap<String, String> queryParam) {
Set<String> encodedQueryParameters = filterParameters(target, encodeQueryParameters(queryParam));
if (target != null && !encodedQueryParameters.isEmpty() && target.contains("?")) {
return rb.header(HttpHeaders.LOCATION, target + "&" + toString(encodedQueryParameters));
}else if(target != null && !encodedQueryParameters.isEmpty()){
return rb.header(HttpHeaders.LOCATION, target + "?" + toString(encodedQueryParameters));
}else if(target != null){
return rb.header(HttpHeaders.LOCATION, target);
}else{
return rb;
}
}
/**
* Add an E-Tag header
* @param rb response builder
* @param entityTag etag
* @return response builder
*/
public static ResponseBuilder etagHeader(ResponseBuilder rb, String entityTag) {
if (entityTag != null && !entityTag.isEmpty()) {
return rb.header(HttpHeaders.ETAG, entityTag);
}
return rb;
}
public static ResponseBuilder maxAgeHeader(ResponseBuilder rb, int maxAge) {
return rb.header(HttpHeaders.CACHE_CONTROL, "max-age=" + maxAge );
}
/**
* Returns the first HTTP header entry for the specified header
* @param headers HTTP headers
* @param header header to return
* @return first header entry
*/
public static String getFirstHeader(HttpHeaders headers, String header) {
if (headers == null) {
return null;
}
List<String> headerList = headers.getRequestHeader(header);
if(headerList != null && headerList.size() > 0) {
return headerList.get(0);
}
return null;
}
/**
* Returns the first HTTP header entry for the specified header ignoring case,
* that is, it does a case insensitive search.
*
* @param headers HTTP headers
* @param header header to return
* @return first header entry
*/
public static String getFirstHeaderCaseInsensitive(HttpHeaders headers, String header) {
if (headers == null) {
return null;
}
MultivaluedMap<String, String> headerMap = headers.getRequestHeaders();
if (headerMap == null) {
return null;
}
for (String key : headerMap.keySet()) {
if (key.equalsIgnoreCase(header)) {
return headerMap.getFirst(key);
}
}
return null;
}
/**
* Encode a MultivaluedMap as URL query parameters, omitting any duplicate
* key/value pairings.
* @param requestParameters The query parameters to encode.
* @return An encoded query string suitable for use with a URL.
*/
public static String encodeMultivalueQueryParameters(
MultivaluedMap<String, String> queryParam){
if(isNullOrEmpty(queryParam)){
return "";
}
return toString(encodeQueryParameters(queryParam));
}
private static Set<String> encodeQueryParameters(MultivaluedMap<String, String> queryParameters) {
Set<String> encodedQueryParameters = new HashSet<>();
if (queryParameters == null) {
return encodedQueryParameters;
}
int outerIndex = 0;
List<String> filter = new ArrayList<>();
for(Map.Entry<String, List<String>> parameterKeyAndValues : queryParameters.entrySet()){
filterDuplicateQueryKeyValuePairings(parameterKeyAndValues.getValue(), filter);
String keyAndValue = constructQueryKeyValuePairing(parameterKeyAndValues.getKey(), filter, outerIndex < queryParameters.size() - 1);
encodedQueryParameters.add(keyAndValue);
filter.clear();
outerIndex++;
}
return encodedQueryParameters;
}
private static Set<String> filterParameters(String target, Set<String> parameters) {
Set<String> filteredParameters = new HashSet<>();
for (String parameter : parameters) {
if (!target.contains(parameter)) {
filteredParameters.add(parameter);
}
}
return filteredParameters;
}
private static boolean isNullOrEmpty(MultivaluedMap<String, String> queryParam){
return queryParam == null || queryParam.size() == 0;
}
private static void filterDuplicateQueryKeyValuePairings(List<String> src, List<String> dest){
for(String value : src){
if(!dest.contains(value)){
dest.add(value);
}
}
if(dest.isEmpty()){
dest.add(""); //interpret empty list as a key without a value (e.g. ?x=&y=)
}
}
private static String constructQueryKeyValuePairing(String key, List<String> values, boolean appendAmpersand) {
StringBuilder sb = new StringBuilder();
int index = 0;
for(String value : values){
sb.append(encodeQueryParameter(key, DEFAULT_ENCODING));
sb.append("=");
sb.append(encodeQueryParameter(value, DEFAULT_ENCODING));
if(index < values.size() - 1){
sb.append("&");
}
index++;
}
if(appendAmpersand && sb.length() > 0){
sb.append("&");
}
return sb.toString();
}
private static String encodeQueryParameter(String queryParam, String encoding){
try{
return URLEncoder.encode(queryParam, encoding);
}catch(UnsupportedEncodingException uee){
logger.error("Unsupported encoding type {} used to encode {}",
encoding, queryParam);
throw new RuntimeException(uee);
}
}
private static String toString(Set<String> set) {
StringBuilder sb = new StringBuilder();
for(String s : set){
sb.append(s);
}
return sb.toString();
}
}