/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Mar 6, 2012
*/
package com.bigdata.rdf.sail.webapp;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.openrdf.query.resultio.BooleanQueryResultFormat;
import org.openrdf.query.resultio.TupleQueryResultFormat;
import org.openrdf.rio.RDFFormat;
import com.bigdata.counters.format.CounterSetFormat;
import com.bigdata.rdf.properties.PropertiesFormat;
import com.bigdata.rdf.sail.webapp.client.IMimeTypes;
import com.bigdata.rdf.sail.webapp.client.MiniMime;
/**
* Helper class for content negotiation.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
public class ConnegUtil {
static private final transient Logger log = Logger
.getLogger(ConnegUtil.class);
private static final Pattern pattern;
static {
pattern = Pattern.compile("\\s*,\\s*");
}
// static {
// // Work-around for sesame not handling ask and json (see trac 704 and
// 714)
//
// if
// (BooleanQueryResultFormat.forMIMEType(BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON)!=null)
// {
// // This should fire once trac 714 is fixed, and we have upgraded, at this
// point the whole static block should be deleted.
// log.warn("Workaround for sesame 2.6 BooleanQueryResultFormat defect no longer needed",
// new RuntimeException("location of issue"));
// } else {
// final BooleanQueryResultFormat askJsonFormat =
// BooleanQueryResultFormat.register("SPARQL/JSON",BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON,"srj");
// BooleanQueryResultWriterRegistry.getInstance().add(new
// BooleanQueryResultWriterFactory(){
//
// @Override
// public BooleanQueryResultFormat getBooleanQueryResultFormat() {
// return askJsonFormat;
// }
//
// @Override
// public BooleanQueryResultWriter getWriter(final OutputStream out) {
// return new BooleanQueryResultWriter(){
//
// @Override
// public BooleanQueryResultFormat getBooleanQueryResultFormat() {
// return askJsonFormat;
// }
//
// @Override
// public void write(boolean arg0) throws IOException {
// final String answer = "{ \"head\":{ } , \"boolean\": " +
// Boolean.toString(arg0) + " }";
// out.write(answer.getBytes("utf-8"));
// }};
// }});
// }
// }
private final ConnegScore<?>[] scores;
/**
* A utility method to check for a format string query parameter and update
* the mimetype for sending back results. This is to enable the use of the
* ?format=json style of interaction. See Trac Ticket #984.
*
* Checks if a query parameter "out" has been passed to set the response
* type. If found, it maps to the requested Accept Header. It currently
* supports json, sparql-results+json, xml, and sparql-results+xml.
*
*
* @param req
* @return The value of the format string, if present or the Accept Header.
*
* @see http://trac.blazegraph.com/ticket/984
*/
public static String getMimeTypeForQueryParameterQueryRequest(final String outputFormat,
final String... acceptHeaders) {
String acceptHeaderValue = null;
if (outputFormat != null) {
switch (outputFormat.toLowerCase()) {
case BigdataRDFServlet.OUTPUT_FORMAT_JSON_SHORT:
acceptHeaderValue = BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON;
break;
case BigdataRDFServlet.OUTPUT_FORMAT_XML_SHORT:
acceptHeaderValue = BigdataRDFServlet.MIME_SPARQL_RESULTS_XML;
break;
case BigdataRDFServlet.OUTPUT_FORMAT_JSON:
acceptHeaderValue = BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON;
break;
case BigdataRDFServlet.OUTPUT_FORMAT_XML:
acceptHeaderValue = BigdataRDFServlet.MIME_SPARQL_RESULTS_XML;
break;
default:
acceptHeaderValue = BigdataRDFServlet.MIME_SPARQL_RESULTS_XML;
log.warn("Unknown value for QUERY PARAMETER: "
+ BigdataRDFServlet.OUTPUT_FORMAT_QUERY_PARAMETER
+ " passed " + outputFormat + ". Defaulting to XML.");
}
} else {
StringBuilder sb = new StringBuilder();
for(String acceptHeader: acceptHeaders) {
if (sb.length()>0) {
sb.append(", ");
}
sb.append(acceptHeader);
}
acceptHeaderValue = sb.toString();
}
return acceptHeaderValue;
}
/**
* A utility method to check for a format string query parameter and update
* the mimetype for sending back results. This is to enable the use of the
* ?format=json style of interaction. See Trac Ticket #984.
*
* Checks if a query parameter "out" has been passed to set the response
* type. If found, it maps to the requested Accept Header. It currently
* supports json and xml.
*
* This method should be used for general HTTP requests rather than Sparql
* query results.
*
* TODO: Support HTML, XML, and RDF
*
*
* @param req
* @return The value of the format string, if present or the Accept Header.
*
* @see http://trac.blazegraph.com/ticket/984
*/
public static String getMimeTypeForQueryParameterServiceRequest(String outputFormat,
String acceptHeader) {
String acceptHeaderValue = null;
if (outputFormat != null) {
switch (outputFormat.toLowerCase()) {
case BigdataRDFServlet.OUTPUT_FORMAT_JSON_SHORT:
acceptHeaderValue = BigdataRDFServlet.MIME_JSON;
break;
case BigdataRDFServlet.OUTPUT_FORMAT_XML_SHORT:
acceptHeaderValue = BigdataRDFServlet.MIME_SPARQL_RESULTS_XML;
break;
default: //Keep HTML as the default for now for legacy compatibility with workbench
acceptHeaderValue = IMimeTypes.MIME_TEXT_HTML;
log.warn("Unknown value for QUERY PARAMETER: "
+ BigdataRDFServlet.OUTPUT_FORMAT_QUERY_PARAMETER
+ " passed " + outputFormat + ". Defaulting to XML.");
}
} else {
acceptHeaderValue = acceptHeader;
}
return acceptHeaderValue;
}
/**
*
* @param acceptStr
* The <code>Accept</code> header.
*/
public ConnegUtil(String acceptStr) {
if (acceptStr == null) {
/*
* If no Accept header is present, then the client has no
* preferences.
*/
// throw new IllegalArgumentException();
acceptStr = "";
}
final String[] a = pattern.split(acceptStr);
final List<ConnegScore<?>> scores = new LinkedList<ConnegScore<?>>();
{
for (String s : a) {
final MiniMime t = new MiniMime(s);
// RDFFormat
{
final RDFFormat rdfFormat = RDFFormat.forMIMEType(t
.getMimeType());
if (rdfFormat != null) {
scores.add(new ConnegScore<RDFFormat>(t.q, rdfFormat));
}
}
// TupleQueryResultFormat
{
final TupleQueryResultFormat tupleFormat = TupleQueryResultFormat
.forMIMEType(t.getMimeType());
if (tupleFormat != null) {
scores.add(new ConnegScore<TupleQueryResultFormat>(t.q,
tupleFormat));
}
}
// BooleanQueryResultFormat
{
BooleanQueryResultFormat booleanFormat = BooleanQueryResultFormat
.forMIMEType(t.getMimeType());
if (booleanFormat != null) {
scores.add(new ConnegScore<BooleanQueryResultFormat>(
t.q, booleanFormat));
}
}
// PropertiesFormat
{
final PropertiesFormat format = PropertiesFormat
.forMIMEType(t.getMimeType());
if (format != null) {
scores.add(new ConnegScore<PropertiesFormat>(t.q,
format));
}
}
// CounterSetFormat
{
final CounterSetFormat format = CounterSetFormat
.forMIMEType(t.getMimeType());
if (format != null) {
scores.add(new ConnegScore<CounterSetFormat>(t.q,
format));
}
}
}
}
this.scores = scores.toArray(new ConnegScore[scores.size()]);
// Order by quality.
Arrays.sort(this.scores);
}
/**
* Return the best {@link RDFFormat} from the <code>Accept</code> header,
* where "best" is measured by the <code>q</code> parameter.
*
* @return The best {@link RDFFormat} -or- <code>null</code> if no
* {@link RDFFormat} was requested.
*/
public RDFFormat getRDFFormat() {
return getRDFFormat(null/* fallback */);
}
/**
* Return the best {@link RDFFormat} from the <code>Accept</code> header,
* where "best" is measured by the <code>q</code> parameter.
*
* @param fallback
* The caller's default, which is returned if no match was
* specified.
*
* @return The best {@link RDFFormat} -or- <i>fallback</i> if no
* {@link RDFFormat} was requested.
*/
public RDFFormat getRDFFormat(final RDFFormat fallback) {
for (ConnegScore<?> s : scores) {
if (s.format instanceof RDFFormat) {
return (RDFFormat) s.format;
}
}
return fallback;
}
/**
* Return the best {@link TupleQueryResultFormat} from the
* <code>Accept</code> header, where "best" is measured by the
* <code>q</code> parameter.
*
* @return The best {@link TupleQueryResultFormat} -or- <code>null</code> if
* no {@link TupleQueryResultFormat} was requested.
*/
public TupleQueryResultFormat getTupleQueryResultFormat() {
return getTupleQueryResultFormat(null/* fallback */);
}
/**
* Return the best {@link TupleQueryResultFormat} from the
* <code>Accept</code> header, where "best" is measured by the
* <code>q</code> parameter.
*
* @param fallback
* The caller's default, which is returned if no match was
* specified.
*
* @return The best {@link TupleQueryResultFormat} -or- <i>fallback</i> if
* no {@link TupleQueryResultFormat} was requested.
*/
public TupleQueryResultFormat getTupleQueryResultFormat(
final TupleQueryResultFormat fallback) {
for (ConnegScore<?> s : scores) {
if (s.format instanceof TupleQueryResultFormat) {
return (TupleQueryResultFormat) s.format;
}
}
return fallback;
}
/**
* Return the best {@link BooleanQueryResultFormat} from the
* <code>Accept</code> header, where "best" is measured by the
* <code>q</code> parameter.
*
* @return The best {@link BooleanQueryResultFormat} -or- <code>null</code>
* if no {@link BooleanQueryResultFormat} was requested.
*/
public BooleanQueryResultFormat getBooleanQueryResultFormat() {
return getBooleanQueryResultFormat(null/* fallback */);
}
/**
* Return the best {@link BooleanQueryResultFormat} from the
* <code>Accept</code> header, where "best" is measured by the
* <code>q</code> parameter.
*
* @param fallback
* The caller's default, which is returned if no match was
* specified.
*
* @return The best {@link BooleanQueryResultFormat} -or- <i>fallback</i> if
* no {@link BooleanQueryResultFormat} was requested.
*/
public BooleanQueryResultFormat getBooleanQueryResultFormat(
final BooleanQueryResultFormat fallback) {
for (ConnegScore<?> s : scores) {
if (s.format instanceof BooleanQueryResultFormat) {
return (BooleanQueryResultFormat) s.format;
}
}
return fallback;
}
/**
* Return the best {@link PropertiesFormat} from the <code>Accept</code>
* header, where "best" is measured by the <code>q</code> parameter.
*
* @return The best {@link PropertiesFormat} -or- <code>null</code> if no
* {@link PropertiesFormat} was requested.
*/
public PropertiesFormat getPropertiesFormat() {
return getPropertiesFormat(null/* fallback */);
}
/**
* Return the best {@link PropertiesFormat} from the <code>Accept</code>
* header, where "best" is measured by the <code>q</code> parameter.
*
* @param fallback
* The caller's default, which is returned if no match was
* specified.
*
* @return The best {@link PropertiesFormat} -or- <i>fallback</i> if no
* {@link PropertiesFormat} was requested.
*/
public PropertiesFormat getPropertiesFormat(final PropertiesFormat fallback) {
for (ConnegScore<?> s : scores) {
if (s.format instanceof PropertiesFormat) {
return (PropertiesFormat) s.format;
}
}
return fallback;
}
/**
* Return the best {@link CounterSetFormat} from the <code>Accept</code>
* header, where "best" is measured by the <code>q</code> parameter.
*
* @return The best {@link CounterSetFormat} -or- <code>null</code> if no
* {@link CounterSetFormat} was requested.
*/
public CounterSetFormat getCounterSetFormat() {
return getCounterSetFormat(null/* fallback */);
}
/**
* Return the best {@link RDCountersFormatFFormat} from the
* <code>Accept</code> header, where "best" is measured by the
* <code>q</code> parameter.
*
* @param fallback
* The caller's default, which is returned if no match was
* specified.
*
* @return The best {@link CounterSetFormat} -or- <i>fallback</i> if no
* {@link CounterSetFormat} was requested.
*/
public CounterSetFormat getCounterSetFormat(final CounterSetFormat fallback) {
for (ConnegScore<?> s : scores) {
if (s.format instanceof CounterSetFormat) {
return (CounterSetFormat) s.format;
}
}
return fallback;
}
/**
* Return an ordered list of the {@link ConnegScore}s for MIME Types which
* are consistent with the desired format type.
*
* @param cls
* The format type.
*
* @return The ordered list.
*/
@SuppressWarnings("unchecked")
public <E> ConnegScore<E>[] getScores(final Class<E> cls) {
if (cls == null)
throw new IllegalArgumentException();
final List<ConnegScore<E>> t = new LinkedList<ConnegScore<E>>();
for (ConnegScore<?> s : scores) {
if (cls == s.format.getClass()) {
t.add((ConnegScore<E>) s);
}
}
return t.toArray(new ConnegScore[t.size()]);
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" + Arrays.toString(scores) + "}";
}
}