blob: 9a4a3c3fba4d335fb2beca2a80b2efe2b6c1eb1a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.clerezza.jaxrs.sparql.providers;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.apache.clerezza.commons.rdf.BlankNode;
import org.apache.clerezza.commons.rdf.RDFTerm;
import org.apache.clerezza.commons.rdf.IRI;
import org.apache.clerezza.rdf.core.sparql.ResultSet;
import org.apache.clerezza.rdf.core.sparql.SolutionMapping;
import org.apache.clerezza.commons.rdf.Literal;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* MessageBodyWirter for <code>ResultSet</code>. Resulting output is csv and
* conforms to:
* http://www.w3.org/TR/2013/REC-sparql11-results-csv-tsv-20130321/#csv
*
* Also see: http://tools.ietf.org/html/rfc4180
*
* @author misl
*/
@Component
@Service( Object.class )
@Property( name = "javax.ws.rs", boolValue = true )
@Produces( { "text/csv" } )
@Provider
public class ResultSetCsvMessageBodyWriter implements MessageBodyWriter<ResultSet> {
private static final Logger logger = LoggerFactory
.getLogger( ResultSetCsvMessageBodyWriter.class );
private String textEncoding = "UTF-8";
private byte[] separator;
public ResultSetCsvMessageBodyWriter() {
try {
buildSeparatorConformEncoding( textEncoding );
} catch( UnsupportedEncodingException e ) {
logger.error( "Developer error", e );
}
}
// --------------------------------------------------------------------------
// Implementing MessageBodyWriter
// --------------------------------------------------------------------------
@Override
public boolean isWriteable( Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType ) {
return ResultSet.class.isAssignableFrom( type );
}
@Override
public long getSize( ResultSet t, Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType ) {
return -1;
}
@Override
public void writeTo( ResultSet resultSet, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream ) throws IOException, WebApplicationException {
// According to spec header is mandatory.
writeCsvHeader( entityStream, resultSet.getResultVars() );
while( resultSet.hasNext() ) {
writeCsvLine( entityStream, resultSet.getResultVars(), resultSet.next() );
}
}
// --------------------------------------------------------------------------
// Public interface
// --------------------------------------------------------------------------
/**
* Sets the text encoding for the resource. This setting must only used
* if the resource response represents text.
*
* @param textEncoding
* character encoding of text body
* @throws UnsupportedEncodingException when the given encoding is not supported.
*/
public void setTextEncoding(String textEncoding) throws UnsupportedEncodingException {
buildSeparatorConformEncoding( textEncoding );
this.textEncoding = textEncoding;
}
/**
* @return text encoding for resource
*/
protected String getTextEncoding() {
return textEncoding;
}
// --------------------------------------------------------------------------
// Private methods
// --------------------------------------------------------------------------
/**
* Builds the column separator according to the given text encoding.
*
* @param encoding the text encoding to be used.
* @throws UnsupportedEncodingException when the given encoding is not supported.
*/
private void buildSeparatorConformEncoding( String encoding ) throws UnsupportedEncodingException {
separator = ",".getBytes( encoding );
}
/**
* Write resultset header to the given output stream.
*
* @param outputStream
* stream to write to.
* @param headers
* the headers to write.
* @throws IOException
*/
private void writeCsvHeader( OutputStream outputStream, List<String> headers ) throws IOException {
boolean first = true;
for( String header : headers ) {
if( !first ) {
outputStream.write( separator );
}
writeEscaped( outputStream, header );
first = false;
}
outputStream.write( "\n".getBytes( textEncoding ) );
}
/**
* Write a single csv line using the given line data.
*
* @param outputStream
* stream to write to.
* @param headers
* the headers to write line data for.
* @param lineData
* the line data to write.
* @throws IOException
*/
private void writeCsvLine( OutputStream outputStream, List<String> headers,
SolutionMapping lineData ) throws IOException {
boolean first = true;
for( String header : headers ) {
if( !first ) {
outputStream.write( separator );
}
RDFTerm resource = lineData.get( header );
if( resource != null ) {
writeEscaped( outputStream, getResourceValue( resource ) );
}
first = false;
}
outputStream.write( "\n".getBytes( textEncoding ) );
}
/**
* Helper to get the proper string representation for the given RDFTerm.
*/
private String getResourceValue( RDFTerm resource ) {
StringBuilder value = new StringBuilder();
if( resource instanceof IRI ) {
value.append(((IRI) resource).getUnicodeString() );
} else if( resource instanceof Literal ) {
value.append( ((Literal) resource).getLexicalForm() );
} else if( resource instanceof BlankNode ) {
value.append( "/" );
} else {
value.append( resource.toString() );
}
return value.toString();
}
/**
* Write the given string to the output stream and escape the output where
* necessary.
*
* @param outputStream
* stream to write to.
* @param text
* the text to write.
* @throws IOException
*/
private void writeEscaped( OutputStream outputStream, String text ) throws IOException {
String line = text;
if( text.contains( "\r" ) || text.contains( "\n" ) || text.contains( "," )
|| text.contains( "\"" ) ) {
StringBuilder builder = new StringBuilder();
builder.append( '"' );
builder.append( text.replaceAll( "\"", "\"\"" ) );
builder.append( '"' );
line = builder.toString();
}
outputStream.write( line.getBytes( textEncoding ) );
}
}