blob: 742ca7d0faee968144e44ba7000e222ed1a271ec [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.jena.fuseki.system;
import static java.lang.String.format;
import static org.apache.jena.riot.WebContent.ctMultipartFormData;
import static org.apache.jena.riot.WebContent.ctTextPlain;
import static org.apache.jena.riot.WebContent.matchContentType;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.jena.atlas.io.IO;
import org.apache.jena.atlas.web.ContentType;
import org.apache.jena.fuseki.servlets.ActionErrorException;
import org.apache.jena.fuseki.servlets.ActionLib;
import org.apache.jena.fuseki.servlets.HttpAction;
import org.apache.jena.fuseki.servlets.ServletOps;
import org.apache.jena.iri.IRI;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFLanguages;
import org.apache.jena.riot.RiotParseException;
import org.apache.jena.riot.lang.StreamRDFCounting;
import org.apache.jena.riot.system.IRIResolver;
import org.apache.jena.riot.system.StreamRDF;
import org.apache.jena.riot.system.StreamRDFLib;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.DatasetGraphFactory;
public class Upload {
/** Parse the body contents to the {@link StreamRDF}.
* This function is used by GSP.
*/
public static UploadDetails incomingData(HttpAction action, StreamRDF dest) {
ContentType ct = ActionLib.getContentType(action);
if ( ct == null ) {
ServletOps.errorBadRequest("No content type");
return null;
}
if ( matchContentType(ctMultipartFormData, ct) ) {
return fileUploadWorker(action, dest);
}
// Single graph (or quads) in body.
String base = ActionLib.wholeRequestURL(action.request);
Lang lang = RDFLanguages.contentTypeToLang(ct.getContentTypeStr());
if ( lang == null ) {
ServletOps.errorBadRequest("Unknown content type for triples: " + ct);
return null;
}
InputStream input = null;
try { input = action.request.getInputStream(); }
catch (IOException ex) { IO.exception(ex); }
long len = action.request.getContentLengthLong();
StreamRDFCounting countingDest = StreamRDFLib.count(dest);
try {
ActionLib.parse(action, countingDest, input, lang, base);
UploadDetails details = new UploadDetails(countingDest.count(), countingDest.countTriples(),countingDest.countQuads());
action.log.info(format("[%d] Body: Content-Length=%d, Content-Type=%s, Charset=%s => %s : %s",
action.id, len, ct.getContentTypeStr(), ct.getCharset(), lang.getName(),
details.detailsStr()));
return details;
} catch (RiotParseException ex) {
action.log.info(format("[%d] Body: Content-Length=%d, Content-Type=%s, Charset=%s => %s : %s",
action.id, len, ct.getContentTypeStr(), ct.getCharset(), lang.getName(),
ex.getMessage()));
throw ex;
}
}
/**
* Process an HTTP upload of RDF files (triples or quads)
* Stream straight into the destination graph or dataset, ignoring any
* headers in the form parts. This function is used by GSP.
*/
public static UploadDetails fileUploadWorker(HttpAction action, StreamRDF dest) {
String base = ActionLib.wholeRequestURL(action.request);
ServletFileUpload upload = new ServletFileUpload();
StreamRDFCounting countingDest = StreamRDFLib.count(dest);
try {
FileItemIterator iter = upload.getItemIterator(action.request);
while (iter.hasNext()) {
FileItemStream fileStream = iter.next();
if (fileStream.isFormField()) {
// Ignore?
String fieldName = fileStream.getFieldName();
InputStream stream = fileStream.openStream();
String value = Streams.asString(stream, "UTF-8");
// This code is currently used to put multiple files into a single destination.
// Additonal field/values do not make sense.
ServletOps.errorBadRequest(format("Only files accepted in multipart file upload (got %s=%s)",fieldName, value));
}
//Ignore the field name.
//String fieldName = fileStream.getFieldName();
InputStream stream = fileStream.openStream();
// Process the input stream
String contentTypeHeader = fileStream.getContentType();
ContentType ct = ContentType.create(contentTypeHeader);
Lang lang = null;
if ( ! matchContentType(ctTextPlain, ct) )
lang = RDFLanguages.contentTypeToLang(ct.getContentTypeStr());
if ( lang == null ) {
String name = fileStream.getName();
if ( name == null || name.equals("") )
ServletOps.errorBadRequest("No name for content - can't determine RDF syntax");
lang = RDFLanguages.filenameToLang(name);
if (name.endsWith(".gz"))
stream = new GZIPInputStream(stream);
}
if ( lang == null )
// Desperate.
lang = RDFLanguages.RDFXML;
String printfilename = fileStream.getName();
if ( printfilename == null || printfilename.equals("") )
printfilename = "<none>";
// Before
// action.log.info(format("[%d] Filename: %s, Content-Type=%s, Charset=%s => %s",
// action.id, printfilename, ct.getContentType(), ct.getCharset(), lang.getName()));
// count just this step
StreamRDFCounting countingDest2 = StreamRDFLib.count(countingDest);
try {
ActionLib.parse(action, countingDest2, stream, lang, base);
UploadDetails details1 = new UploadDetails(countingDest2.count(), countingDest2.countTriples(),countingDest2.countQuads());
action.log.info(format("[%d] Filename: %s, Content-Type=%s, Charset=%s => %s : %s",
action.id, printfilename, ct.getContentTypeStr(), ct.getCharset(), lang.getName(),
details1.detailsStr()));
} catch (RiotParseException ex) {
action.log.info(format("[%d] Filename: %s, Content-Type=%s, Charset=%s => %s : %s",
action.id, printfilename, ct.getContentTypeStr(), ct.getCharset(), lang.getName(),
ex.getMessage()));
throw ex;
}
}
}
catch (ActionErrorException ex) { throw ex; }
catch (Exception ex) { ServletOps.errorOccurred(ex.getMessage()); }
// Overall results.
UploadDetails details = new UploadDetails(countingDest.count(), countingDest.countTriples(),countingDest.countQuads());
return details;
}
// TODO Merge/replace with Upload.fileUploadWorker
// This code sets the StreamRDF destination during processing and can handle multiple
// files to multiple destinations for example, graphs within a dataset.
// The only functional difference is the single destination (fileUploadWorker) and
// switching on graph name (multipartUploadWorker).
/**
* Process an HTTP file upload of RDF using the name field for the graph name destination.
* This function is used by SPARQL_Upload for {@code fuseki:serviceUpload}.
*/
public static UploadDetailsWithName multipartUploadWorker(HttpAction action, String base) {
DatasetGraph dsgTmp = DatasetGraphFactory.create();
ServletFileUpload upload = new ServletFileUpload();
String graphName = null;
boolean isQuads = false;
long count = -1;
String name = null;
ContentType ct = null;
Lang lang = null;
try {
FileItemIterator iter = upload.getItemIterator(action.request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String fieldName = item.getFieldName();
InputStream stream = item.openStream();
if ( item.isFormField() ) {
// Graph name.
String value = Streams.asString(stream, "UTF-8");
if ( fieldName.equals(HttpNames.paramGraph) ) {
graphName = value;
if ( graphName != null && !graphName.equals("") && !graphName.equals(HttpNames.valueDefault) ) {
// -- Check IRI with additional checks.
IRI iri = IRIResolver.parseIRI(value);
if ( iri.hasViolation(false) )
ServletOps.errorBadRequest("Bad IRI: " + graphName);
if ( iri.getScheme() == null )
ServletOps.errorBadRequest("Bad IRI: no IRI scheme name: " + graphName);
if ( iri.getScheme().equalsIgnoreCase("http") || iri.getScheme().equalsIgnoreCase("https") ) {
// Redundant??
if ( iri.getRawHost() == null )
ServletOps.errorBadRequest("Bad IRI: no host name: " + graphName);
if ( iri.getRawPath() == null || iri.getRawPath().length() == 0 )
ServletOps.errorBadRequest("Bad IRI: no path: " + graphName);
if ( iri.getRawPath().charAt(0) != '/' )
ServletOps.errorBadRequest("Bad IRI: Path does not start '/': " + graphName);
}
// End check IRI
}
} else if ( fieldName.equals(HttpNames.paramDefaultGraphURI) )
graphName = null;
else
// Add file type?
action.log.info(format("[%d] Upload: Field=%s ignored", action.id, fieldName));
} else {
// Process the input stream
name = item.getName();
if ( name == null || name.equals("") || name.equals("UNSET FILE NAME") )
ServletOps.errorBadRequest("No name for content - can't determine RDF syntax");
String contentTypeHeader = item.getContentType();
ct = ContentType.create(contentTypeHeader);
lang = RDFLanguages.contentTypeToLang(ct.getContentTypeStr());
if ( lang == null ) {
lang = RDFLanguages.filenameToLang(name);
// JENA-600 filenameToLang() strips off certain
// extensions such as .gz and
// we need to ensure that if there was a .gz extension
// present we wrap the stream accordingly
if ( name.endsWith(".gz") )
stream = new GZIPInputStream(stream);
}
if ( lang == null )
// Desperate.
lang = RDFLanguages.RDFXML;
isQuads = RDFLanguages.isQuads(lang);
action.log.info(format("[%d] Upload: Filename: %s, Content-Type=%s, Charset=%s => %s", action.id, name,
ct.getContentTypeStr(), ct.getCharset(), lang.getName()));
StreamRDF x = StreamRDFLib.dataset(dsgTmp);
StreamRDFCounting dest = StreamRDFLib.count(x);
ActionLib.parse(action, dest, stream, lang, base);
count = dest.count();
}
}
if ( graphName == null || graphName.equals("") )
graphName = HttpNames.valueDefault;
if ( isQuads )
graphName = null;
return new UploadDetailsWithName(graphName, dsgTmp, count);
}
catch (ActionErrorException ex) { throw ex; }
catch (Exception ex) { ServletOps.errorOccurred(ex); return null; }
}
}