/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.rdf.negotiation;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
/**
*
* @author Pascal-Nicolas Becker (dspace -at- pascal -hyphen- becker -dot- de)
*/
public class MediaRange
{
// defined in RFC 2616
public static final double DEFAULT_QVALUE = 1.0;
// RFC 2616 defines syntax of the accept header using several patterns
// the patterns are defined in the parts 2.2, 3.6, 3.7, 3.9 and 14.1 of the rfc
// SEPARATOR: ( ) < > @ , ; : \ " / [ ] ? = { } <space> <tabulator>
// the separators can be used in as class inside square brackets. To be able
// to negate the class, the spearators necessary square brackets are not
// included in the string.
public static final String separators = "()<>@,;:\\\\\"/\\[\\]?={} \\t";
// TOKEN: ANY US ASCII except ctl an separtor
public static final String token = "[\\040-\\0176" + "&&[^" + separators + "]]+";
// "\" followed by any US ASCII character (octets 0 - 177)
public static final String quotedPair = "(?:\\\\[\\00-\\0177])";
// any 8 bit sequence, except CTLs (00-037, 0177) and " (042) but including LWS
public static final String qdtext = "(?:[\\040\\041\\043-\\0176\\0178-\\0377]|"
+ "(?:\\r\\n)?[ \\t]+)";
// ( <"> *(qdtext | quoted-pair) <">
public static final String quotedString = "(?:\"(?:" + qdtext + "|" + quotedPair + ")*\")";
public static final String nonQualityParam = "(?:\\s*;\\s*(?!q\\s*=)(" + token + ")="
+ "(" + token + "|" + quotedString + ")" + ")";
public static final String qualityParam = "(?:;\\s*q\\s*=\\s*(0(?:\\.\\d{0,3})?|1(?:\\.0{0,3})?))";
// group 0 contains the hole matched media range
// group 1 contains the type
// group 2 contains the subtype
// group 3 contains all parameters before the quality parameter if any
// group 4 contains the name of the last parameter before the quality parameter if any
// group 5 contains the value of the last parameter before the quality parameter if any
// group 6 contains the quality value if any
// group 7 contains all parameters after the quality parameter if any
// group 8 contains the name of the last parameter after the quality paremeter if any
// group 9 contains the value of the laster parameter after the quality paremeter if any
public static final String mediaRangeRegex = "(?:(" + token + ")/(" + token + "?)"
+ "(" + nonQualityParam + "*)" + qualityParam + "?(" + nonQualityParam + "*))";
private final static Logger log = Logger.getLogger(MediaRange.class);
protected final String type;
protected final String subtype;
protected final double qvalue;
// would be good to take a Map for the parameters, but if we get multiple
// parameters with the same name, we would have a problem.
protected final List<String> parameterNames;
protected final List<String> parameterValues;
private MediaRange() {
throw new RuntimeException("Default constructor of MediaRange must "
+ "not be called. Use static methods instead.");
}
public MediaRange(String mediarange)
throws IllegalArgumentException, IllegalStateException
{
Pattern mediaRangePattern = Pattern.compile("^" + mediaRangeRegex + "$");
Pattern nonQualityParamPattern = Pattern.compile(nonQualityParam);
Pattern qualityParamPattern = Pattern.compile(qualityParam);
Matcher rangeMatcher = mediaRangePattern.matcher(mediarange.trim());
if (!rangeMatcher.matches())
{
log.warn("Provided media range ('" + mediarange.trim() + "') "
+ "does not comply with RFC 2616.");
throw new IllegalArgumentException("Provided media range ('"
+ mediarange + "') does not comply with RFC 2616.");
}
String type = rangeMatcher.group(1);
String subtype = rangeMatcher.group(2);
if (StringUtils.isEmpty(type) || StringUtils.isEmpty(subtype))
{
throw new IllegalArgumentException("A media range had an unparsable type or subtype.");
}
type = type.trim().toLowerCase();
subtype = subtype.trim().toLowerCase();
if (type.equals("*") && !subtype.equals("*"))
{
throw new IllegalArgumentException("A media range's type cannot "
+ "be wildcarded if its subtype isn't as well.");
}
// initalize with defualt value, parse later
double qvalue = DEFAULT_QVALUE;
// initialize empty lists, parse parameters later
List<String> parameterNames = new ArrayList<>();
List<String> parameterValues = new ArrayList<>();
// parse qvalue
if (!StringUtils.isEmpty(rangeMatcher.group(6)))
{
// parse provided quality value
try
{
qvalue = Double.parseDouble(rangeMatcher.group(6));
}
catch (NumberFormatException ex)
{
// the regex should assure that the qvalue is parseable.
// if we get a NumberFormatException, we did something terribly
// wrong.
log.fatal("A quality value ('" + rangeMatcher.group(6) + "') "
+ "was unparsable. We probably have a problem with our "
+ "regex!", ex);
throw new IllegalStateException(ex);
}
}
// parse parameters
StringBuilder sb = new StringBuilder();
if (!StringUtils.isEmpty(rangeMatcher.group(3)))
{
sb.append(rangeMatcher.group(3));
}
if (!StringUtils.isEmpty(rangeMatcher.group(7)))
{
sb.append(rangeMatcher.group(7));
}
if (sb.length() > 0)
{
String unparsedParameters = sb.toString();
Pattern paramPattern = Pattern.compile(nonQualityParam);
Matcher m = paramPattern.matcher(unparsedParameters);
if (!m.matches())
{
// the mediarange string matched our mediaRangeRegex, but the
// parsed parameters doesn't?!
log.fatal("Unable to parse the parameters ('"
+ unparsedParameters + "') of a previously parsed media "
+ "range!");
throw new IllegalStateException("Run into problems while parsing "
+ "a substring of a previuosly succesfully parsed string.");
}
while (m.find())
{
if (!StringUtils.isEmpty(m.group(1)))
{
parameterNames.add(m.group(1).trim().toLowerCase());
parameterValues.add(StringUtils.isEmpty(m.group(2)) ? "" : m.group(2).trim());
}
}
}
this.type = type;
this.subtype = subtype;
this.qvalue = qvalue;
this.parameterNames = parameterNames;
this.parameterValues = parameterValues;
}
public double getQvalue() {
return this.qvalue;
}
public String getType() {
return type;
}
public String getSubtype() {
return subtype;
}
public List<String> getParameterNames() {
return parameterNames;
}
public List<String> getParameterValues() {
return parameterValues;
}
public boolean typeIsWildcard()
{
return (StringUtils.equals(type, "*"));
}
public boolean subtypeIsWildcard()
{
return (StringUtils.equals(subtype, "*"));
}
}