blob: 4010bf6744fe3b41637ae044f37373266aa5a765 [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.servlets;
import static java.lang.String.format;
import org.apache.jena.atlas.logging.FmtLog;
import org.apache.jena.fuseki.Fuseki;
import org.apache.jena.fuseki.server.Endpoint;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.iri.IRI;
import org.apache.jena.riot.RiotException;
import org.apache.jena.riot.out.NodeFmtLib;
import org.apache.jena.riot.system.IRIResolver;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.sparql.core.DatasetGraph;
/**
* Target of GSP operations.<br/>
* Extensions: "?graph=union" and "?graph=default"
*
*/
public class GraphTarget {
public final static GraphTarget determineTarget(DatasetGraph dsg, HttpAction action) {
return determineTarget(dsg, action, false);
}
/** With GSP direct naming. */
public final static GraphTarget determineTargetGSP(DatasetGraph dsg, HttpAction action) {
return determineTarget(dsg, action, Fuseki.GSP_DIRECT_NAMING);
}
private final static GraphTarget determineTarget(DatasetGraph dsg, HttpAction action, boolean allowDirectNaming) {
// Inside a transaction.
if ( dsg == null )
ServletOps.errorOccurred("Internal error : No action graph (not in a transaction?)");
// if ( ! dsg.isInTransaction() )
// ServletOps.errorOccurred("Internal error : No transaction");
boolean dftGraph = GSPLib.getOneOnly(action.request, HttpNames.paramGraphDefault) != null;
String uri = GSPLib.getOneOnly(action.request, HttpNames.paramGraph);
if ( !dftGraph && uri == null ) {
// No params - direct naming?
if ( ! allowDirectNaming )
ServletOps.errorBadRequest("Neither default graph nor named graph specified");
// Direct naming.
String directName = action.request.getRequestURL().toString();
if ( action.request.getRequestURI().equals(action.getDatasetName()) )
// No name (should have been a quads operations).
ServletOps.errorBadRequest("Neither default graph nor named graph specified and no direct name");
Node gn = NodeFactory.createURI(directName);
return createNamed(dsg, gn);
}
if ( dftGraph )
return createDefault(dsg);
// Named graph as default
if ( uri.equals(HttpNames.graphTargetDefault) )
// But "named" default
return createDefault(dsg);
// Named graph - union
if ( uri.equals(HttpNames.graphTargetUnion) )
return createUnion(dsg);
String absUri = resolve0(uri, action);
return createNamed(dsg, absUri);
}
// Resolving a relative URI in ?graph= is a bit murky.
// Whether to use the base is the dataset or the dataset+service endpoint name.
// How to find the dataset URL when service can be on the dataset directly or a named endpoint.
// And will it match in the dataset named graphs anyway?
/** Check URI, require it to be absolute. */
private static String resolve0(String uri, HttpAction action) {
IRI iri = IRIResolver.parseIRI(uri);
if ( iri.hasViolation(false) ) {
action.log.warn(format("[%d] Bad URI <%s> : %s",
action.id, uri, iri.violations(false).next().getShortMessage()));
}
if ( ! iri.isAbsolute() ) {
action.log.warn(format("[%d] URI is not abolute: <%s>", action.id, uri));
}
return uri;
}
/** Resolve URI, Calculate a base URI as the dataset URI and resolve uri against that.*/
private static String resolve(String uri, HttpAction action) {
// Strictly, a bit naughty on the URI resolution, but more sensible.
// Make the base the URI of the dataset.
// Strictly, the base includes service and query string but that is unhelpful.
// wholeRequestURL(request);
String base = action.request.getRequestURL().toString();
Endpoint ep = action.getEndpoint();
if ( ! ep.isUnnamed() && base.endsWith(ep.getName()) ) {
// Remove endpoint name
base = base.substring(0, base.length()-ep.getName().length());
}
// Make sure it ends in "/", treating the dataset as a container.
if ( !base.endsWith("/") )
base = base + "/";
try {
IRI abs = IRIResolver.resolveIRI(uri, base);
if ( abs.hasViolation(false) ) {
FmtLog.warn(Fuseki.actionLog, "Bad URI: '"+uri+"' : "+abs.violations(false).next().getShortMessage());
}
return abs.toString();
} catch (RiotException ex) {
// Bad IRI
ServletOps.errorBadRequest("Bad IRI: " + ex.getMessage());
return null;
}
}
final private boolean isDefault;
final private boolean isUnion;
final private DatasetGraph dsg;
final private Node graphName;
static GraphTarget createNamed(DatasetGraph dsg, String graphName) {
return createNamed(dsg, NodeFactory.createURI(graphName));
}
static GraphTarget createNamed(DatasetGraph dsg, Node graphName) {
return new GraphTarget(false, false, dsg, graphName);
}
static GraphTarget createDefault(DatasetGraph dsg) {
return new GraphTarget(true, false, dsg, null);
}
static GraphTarget createUnion(DatasetGraph dsg) {
return new GraphTarget(false, true, dsg, null);
}
/**
* Create a new GraphTarget which is like the original but aimed at a different
* DatasetGraph
*/
static GraphTarget retarget(GraphTarget target, DatasetGraph dsg) {
GraphTarget target2 = new GraphTarget(target, dsg);
return target2;
}
private GraphTarget(boolean isDefault, boolean isUnion, DatasetGraph dsg, Node graphName) {
if ( ! isUnion && !isDefault && graphName == null )
throw new IllegalArgumentException("Inconsistent: not default, union nor graph name");
else if ( isDefault && graphName != null )
throw new IllegalArgumentException("Inconsistent: default and a graph name");
else if ( isUnion && graphName != null )
throw new IllegalArgumentException("Inconsistent: union and a graph name");
else if ( isDefault && isUnion )
throw new IllegalArgumentException("Inconsistent: default and union graph");
this.isDefault = isDefault;
this.isUnion = isUnion;
this.dsg = dsg;
this.graphName = graphName;
}
private GraphTarget(GraphTarget other, DatasetGraph dsg) {
this.dsg = dsg; // other.dsg; // Retarget
this.isDefault = other.isDefault;
this.isUnion = other.isUnion;
this.graphName = other.graphName;
}
public DatasetGraph dataset() { return dsg; }
public boolean isDefault() { return isDefault; }
public boolean isUnion() { return isUnion; }
public Node graphName() { return graphName; }
/**
* Get a graph for the action - this is not a test for graph existence.
* May return null or an empty graph.
*/
public Graph graph() {
if ( isDefault )
return dsg.getDefaultGraph();
if ( isUnion )
return dsg.getUnionGraph();
return dsg.getGraph(graphName);
}
public boolean exists() {
if ( isDefault || isUnion )
return true;
Graph g = graph();
return g != null && ! g.isEmpty();
}
public String label() {
if ( isDefault )
return "default";
if ( isUnion )
return "union";
return NodeFmtLib.str(graphName);
}
@Override
public String toString() {
return "target:"+label();
}
}