/*
* 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.http;
import java.io.ByteArrayInputStream ;
import java.io.ByteArrayOutputStream ;
import java.io.IOException ;
import java.io.InputStream ;
import org.apache.http.Header ;
import org.apache.http.HttpEntity ;
import org.apache.http.HttpResponse ;
import org.apache.http.HttpVersion ;
import org.apache.http.client.HttpClient ;
import org.apache.http.client.methods.* ;
import org.apache.http.entity.InputStreamEntity ;
import org.apache.http.impl.client.SystemDefaultHttpClient ;
import org.apache.http.params.BasicHttpParams ;
import org.apache.http.params.HttpConnectionParams ;
import org.apache.http.params.HttpParams ;
import org.apache.http.params.HttpProtocolParams ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.atlas.web.TypedInputStream ;
import org.apache.jena.fuseki.* ;
import org.apache.jena.riot.Lang ;
import org.apache.jena.riot.RDFDataMgr ;
import org.apache.jena.riot.WebContent ;
import org.apache.jena.riot.system.IRILib ;
import com.hp.hpl.jena.graph.Graph ;
import com.hp.hpl.jena.graph.Node ;
import com.hp.hpl.jena.rdf.model.Model ;
import com.hp.hpl.jena.rdf.model.ModelFactory ;
import com.hp.hpl.jena.sparql.graph.GraphFactory ;
import com.hp.hpl.jena.sparql.graph.UnmodifiableGraph ;
public class DatasetGraphAccessorHTTP implements DatasetGraphAccessor
{
private final String remote ;
/** Create a DatasetUpdater for the remote URL */
public DatasetGraphAccessorHTTP(String remote)
{
this.remote = remote ;
}
@Override
public Graph httpGet() { return doGet(targetDefault()) ; }
@Override
public Graph httpGet(Node graphName) { return doGet(target(graphName)) ; }
private Graph doGet(String url)
{
HttpUriRequest httpGet = new HttpGet(url) ;
try {
return exec(url, null, httpGet, true) ;
} catch (FusekiNotFoundException ex)
{
return null ;
}
}
@Override
public boolean httpHead()
{
return doHead(targetDefault()) ;
}
@Override
public boolean httpHead(Node graphName)
{
return doHead(target(graphName)) ;
}
private boolean doHead(String url)
{
HttpUriRequest httpHead = new HttpHead(url) ;
try {
exec(url, null, httpHead, false) ;
return true ;
} catch (FusekiRequestException ex)
{
if ( ex.getStatusCode() == HttpSC.NOT_FOUND_404 )
return false ;
throw ex ;
}
}
@Override
public void httpPut(Graph data) { doPut(targetDefault(), data) ; }
@Override
public void httpPut(Node graphName, Graph data) { doPut(target(graphName), data) ; }
private void doPut(String url, Graph data)
{
HttpUriRequest httpPut = new HttpPut(url) ;
exec(url, data, httpPut, false) ;
}
@Override
public void httpDelete() { doDelete(targetDefault()) ; }
@Override
public void httpDelete(Node graphName) { doDelete(target(graphName)) ; }
private boolean doDelete(String url)
{
try {
HttpUriRequest httpDelete = new HttpDelete(url) ;
exec(url, null, httpDelete, false) ;
return true ;
} catch (FusekiNotFoundException ex) { return false ; }
}
@Override
public void httpPost(Graph data) { doPost(targetDefault(), data) ; }
@Override
public void httpPost(Node graphName, Graph data) { doPost(target(graphName), data) ; }
private void doPost(String url, Graph data)
{
HttpUriRequest httpPost = new HttpPost(url) ;
exec(url, data, httpPost, false) ;
}
@Override
public void httpPatch(Graph data) { throw new UnsupportedOperationException() ; }
@Override
public void httpPatch(Node graphName, Graph data) { throw new UnsupportedOperationException() ; }
private String targetDefault()
{
return remote+"?"+HttpNames.paramGraphDefault+"=" ;
}
private String target(Node name)
{
if ( ! name.isURI() )
throw new FusekiException("Not a URI: "+name) ;
String guri = name.getURI() ;
// Encode
guri = IRILib.encodeUriComponent(guri) ;
return remote+"?"+HttpNames.paramGraph+"="+guri ;
}
static private HttpParams httpParams = createHttpParams() ;
static private HttpParams createHttpParams()
{
HttpParams httpParams$ = new BasicHttpParams() ;
// See DefaultHttpClient.createHttpParams
HttpProtocolParams.setVersion(httpParams$, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(httpParams$, WebContent.charsetUTF8);
HttpProtocolParams.setUseExpectContinue(httpParams$, true);
HttpConnectionParams.setTcpNoDelay(httpParams$, true);
HttpConnectionParams.setSocketBufferSize(httpParams$, 32*1024);
HttpProtocolParams.setUserAgent(httpParams$, Fuseki.NAME+"/"+Fuseki.VERSION);
return httpParams$;
}
private static String getHeader(HttpResponse response, String headerName)
{
Header h = response.getLastHeader(headerName) ;
if ( h == null )
return null ;
return h.getValue() ;
}
private Graph exec(String targetStr, Graph graphToSend, HttpUriRequest httpRequest, boolean processBody)
{
HttpClient httpclient = new SystemDefaultHttpClient(httpParams) ;
if ( graphToSend != null )
{
// ??? httpRequest isa Post
// Impedence mismatch - is there a better way?
ByteArrayOutputStream out = new ByteArrayOutputStream() ;
Model model = ModelFactory.createModelForGraph(graphToSend) ;
model.write(out, "RDF/XML") ;
byte[] bytes = out.toByteArray() ;
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()) ;
InputStreamEntity reqEntity = new InputStreamEntity(in, bytes.length) ;
reqEntity.setContentType(WebContent.contentTypeRDFXML) ;
reqEntity.setContentEncoding(WebContent.charsetUTF8) ;
HttpEntity entity = reqEntity ;
((HttpEntityEnclosingRequestBase)httpRequest).setEntity(entity) ;
}
TypedInputStream ts = null ;
// httpclient.getParams().setXXX
try {
HttpResponse response = httpclient.execute(httpRequest) ;
int responseCode = response.getStatusLine().getStatusCode() ;
String responseMessage = response.getStatusLine().getReasonPhrase() ;
if ( HttpSC.isRedirection(responseCode) )
// Not implemented yet.
throw FusekiRequestException.create(responseCode, responseMessage) ;
// Other 400 and 500 - errors
if ( HttpSC.isClientError(responseCode) || HttpSC.isServerError(responseCode) )
throw FusekiRequestException.create(responseCode, responseMessage) ;
if ( responseCode == HttpSC.NO_CONTENT_204) return null ;
if ( responseCode == HttpSC.CREATED_201 ) return null ;
if ( responseCode != HttpSC.OK_200 )
{
Log.warn(this, "Unexpected status code") ;
throw FusekiRequestException.create(responseCode, responseMessage) ;
}
// May not have a body.
String ct = getHeader(response, HttpNames.hContentType) ;
if ( ct == null )
{
HttpEntity entity = response.getEntity() ;
if (entity != null)
{
InputStream instream = entity.getContent() ;
// Read to completion?
instream.close() ;
}
return null ;
}
// Tidy. See ConNeg / MediaType.
String x = getHeader(response, HttpNames.hContentType) ;
String y[] = x.split(";") ;
String contentType = null ;
if ( y[0] != null )
contentType = y[0].trim();
String charset = null ;
if ( y.length > 1 && y[1] != null )
charset = y[1].trim();
// Get hold of the response entity
HttpEntity entity = response.getEntity() ;
if (entity != null)
{
InputStream instream = entity.getContent() ;
// String mimeType = ConNeg.chooseContentType(request, rdfOffer, ConNeg.acceptRDFXML).getAcceptType() ;
// String charset = ConNeg.chooseCharset(request, charsetOffer, ConNeg.charsetUTF8).getAcceptType() ;
ts = new TypedInputStream(instream, contentType, charset, null) ;
}
Graph graph = GraphFactory.createGraphMem() ;
if ( processBody )
readGraph(graph, ts, null) ;
if ( ts != null )
ts.close() ;
Graph graph2 = new UnmodifiableGraph(graph) ;
return graph2 ;
} catch (IOException ex)
{
httpRequest.abort() ;
return null ;
}
}
private void readGraph(Graph graph, TypedInputStream ts, String base)
{
// Yes - we ignore the charset.
// Either it's XML and so the XML parser deals with it, or the
// language determines the charset and the parsers offer InputStreams.
Lang lang = WebContent.contentTypeToLang(ts.getContentType()) ;
if ( lang == null )
throw new FusekiException("Unknown lang for "+ts.getMediaType()) ;
RDFDataMgr.read(graph, base, lang) ;
}
}