/*******************************************************************************
* Copyright 2013 SAP AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.sap.core.odata.core.batch;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import com.sap.core.odata.api.batch.BatchException;
/**
* @author SAP AG
*/
public class AcceptParser {
private static final String BAD_REQUEST = "400";
private static final String ALL = "*";
private static final String REG_EX_QUALITY_FACTOR = "q=((?:1\\.0{0,3})|(?:0\\.[0-9]{0,2}[1-9]))";
private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
private static final Pattern REG_EX_ACCEPT = Pattern.compile("([a-z\\*]+/[a-z0-9\\+\\*\\-=;\\s]+)");
private static final Pattern REG_EX_ACCEPT_WITH_Q_FACTOR = Pattern.compile(REG_EX_ACCEPT + "(?:;" + REG_EX_OPTIONAL_WHITESPACE + REG_EX_QUALITY_FACTOR + ")?");
private static final Pattern REG_EX_ACCEPT_LANGUAGES = Pattern.compile("((?:(?:[a-z]{1,8})|(?:\\*))\\-?(?:[a-zA-Z]{1,8})?)");
private static final Pattern REG_EX_ACCEPT_LANGUAGES_WITH_Q_FACTOR = Pattern.compile(REG_EX_ACCEPT_LANGUAGES + "(?:;" + REG_EX_OPTIONAL_WHITESPACE + REG_EX_QUALITY_FACTOR + ")?");
private static final double QUALITY_PARAM_FACTOR = 0.001;
public static List<String> parseAcceptHeaders(final String headerValue) throws BatchException {
TreeSet<Accept> acceptTree = getAcceptTree();
List<String> acceptHeaders = new ArrayList<String>();
Scanner acceptHeaderScanner = new Scanner(headerValue).useDelimiter(",\\s?");
while (acceptHeaderScanner.hasNext()) {
if (acceptHeaderScanner.hasNext(REG_EX_ACCEPT_WITH_Q_FACTOR)) {
acceptHeaderScanner.next(REG_EX_ACCEPT_WITH_Q_FACTOR);
MatchResult result = acceptHeaderScanner.match();
if (result.groupCount() == 2) {
String acceptHeaderValue = result.group(1);
double qualityFactor = result.group(2) != null ? Double.parseDouble(result.group(2)) : 1d;
qualityFactor = getQualityFactor(acceptHeaderValue, qualityFactor);
Accept acceptHeader = new Accept().setQuality(qualityFactor).setValue(acceptHeaderValue);
acceptTree.add(acceptHeader);
} else {
String header = acceptHeaderScanner.next();
acceptHeaderScanner.close();
throw new BatchException(BatchException.INVALID_ACCEPT_HEADER.addContent(header), BAD_REQUEST);
}
} else {
String header = acceptHeaderScanner.next();
acceptHeaderScanner.close();
throw new BatchException(BatchException.INVALID_ACCEPT_HEADER.addContent(header), BAD_REQUEST);
}
}
for (Accept accept : acceptTree) {
acceptHeaders.add(accept.getValue());
}
acceptHeaderScanner.close();
return acceptHeaders;
}
private static double getQualityFactor(final String acceptHeaderValue, double qualityFactor) {
int paramNumber = 0;
double typeFactor = 0.0;
double subtypeFactor = 0.0;
String[] mediaRange = acceptHeaderValue.split("(?=[^;]+);");
String[] mediaTypes = mediaRange[0].split("/");
if (mediaTypes.length == 2) {
String type = mediaTypes[0];
String subtype = mediaTypes[1];
if (!ALL.equals(type)) {
typeFactor = 0.001;
}
if (!ALL.equals(subtype)) {
subtypeFactor = 0.001;
}
}
if (mediaRange.length == 2) {
String[] parameters = mediaRange[1].split(";\\s?");
paramNumber = parameters.length;
}
qualityFactor = qualityFactor + paramNumber * QUALITY_PARAM_FACTOR + typeFactor + subtypeFactor;
return qualityFactor;
}
public static List<String> parseAcceptableLanguages(final String headerValue) throws BatchException {
List<String> acceptLanguages = new LinkedList<String>();
TreeSet<Accept> acceptTree = getAcceptTree();
Scanner acceptLanguageScanner = new Scanner(headerValue).useDelimiter(",\\s?");
while (acceptLanguageScanner.hasNext()) {
if (acceptLanguageScanner.hasNext(REG_EX_ACCEPT_LANGUAGES_WITH_Q_FACTOR)) {
acceptLanguageScanner.next(REG_EX_ACCEPT_LANGUAGES_WITH_Q_FACTOR);
MatchResult result = acceptLanguageScanner.match();
if (result.groupCount() == 2) {
String languagerange = result.group(1);
double qualityFactor = result.group(2) != null ? Double.parseDouble(result.group(2)) : 1d;
acceptTree.add(new Accept().setQuality(qualityFactor).setValue(languagerange));
} else {
String acceptLanguage = acceptLanguageScanner.next();
acceptLanguageScanner.close();
throw new BatchException(BatchException.INVALID_ACCEPT_LANGUAGE_HEADER.addContent(acceptLanguage), BAD_REQUEST);
}
} else {
String acceptLanguage = acceptLanguageScanner.next();
acceptLanguageScanner.close();
throw new BatchException(BatchException.INVALID_ACCEPT_LANGUAGE_HEADER.addContent(acceptLanguage), BAD_REQUEST);
}
}
for (Accept accept : acceptTree) {
acceptLanguages.add(accept.getValue());
}
acceptLanguageScanner.close();
return acceptLanguages;
}
private static TreeSet<Accept> getAcceptTree() {
TreeSet<Accept> treeSet = new TreeSet<Accept>(new Comparator<Accept>() {
@Override
public int compare(final Accept o1, final Accept o2) {
if (o1.getQuality() <= o2.getQuality()) {
return 1;
} else {
return -1;
}
}
});
return treeSet;
}
private static class Accept {
private double quality;
private String value;
public String getValue() {
return value;
}
public Accept setValue(final String value) {
this.value = value;
return this;
}
public double getQuality() {
return quality;
}
public Accept setQuality(final double quality) {
this.quality = quality;
return this;
}
}
}