http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java new file mode 100644 index 0000000..9e9df36 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java @@ -0,0 +1,60 @@ +/* + * 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 com.hp.hpl.jena.query.Dataset ; +import com.hp.hpl.jena.query.DatasetFactory ; +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.sparql.core.DatasetDescription ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DynamicDatasets ; + +public class SPARQL_QueryDataset extends SPARQL_Query +{ + public SPARQL_QueryDataset(boolean verbose) { super() ; } + + public SPARQL_QueryDataset() + { this(false) ; } + + @Override + protected void validateRequest(HttpAction action) + { } + + @Override + protected void validateQuery(HttpAction action, Query query) + { } + + @Override + protected Dataset decideDataset(HttpAction action, Query query, String queryStringLog) + { + DatasetGraph dsg = action.getActiveDSG() ; + + // query.getDatasetDescription() ; + + // Protocol. + DatasetDescription dsDesc = getDatasetDescription(action) ; + if (dsDesc != null ) + { + //errorBadRequest("SPARQL Query: Dataset description in the protocol request") ; + dsg = DynamicDatasets.dynamicDataset(dsDesc, dsg, false) ; + } + + return DatasetFactory.create(dsg) ; + } +}
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java new file mode 100644 index 0000000..f1c9a9a --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java @@ -0,0 +1,142 @@ +/* + * 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 java.util.List ; + +import org.apache.jena.atlas.lib.InternalErrorException ; +import org.apache.jena.fuseki.migrate.GraphLoadUtils ; +import org.apache.jena.riot.RiotException ; + +import com.hp.hpl.jena.query.Dataset ; +import com.hp.hpl.jena.query.DatasetFactory ; +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.rdf.model.Model ; +import com.hp.hpl.jena.rdf.model.ModelFactory ; +import com.hp.hpl.jena.sparql.core.DatasetDescription ; + +public class SPARQL_QueryGeneral extends SPARQL_Query +{ + final static int MaxTriples = 100*1000 ; + + public SPARQL_QueryGeneral() { super() ; } + + @Override + protected void validateRequest(HttpAction action) {} + + @Override + protected void validateQuery(HttpAction action, Query query) {} + + @Override + protected String mapRequestToDataset(HttpAction action) + { return null ; } + + @Override + protected Dataset decideDataset(HttpAction action, Query query, String queryStringLog) + { + DatasetDescription datasetDesc = getDatasetDescription(action) ; + if ( datasetDesc == null ) + datasetDesc = getDatasetDescription(query) ; + if ( datasetDesc == null ) + ServletOps.errorBadRequest("No dataset description in protocol request or in the query string") ; + + return datasetFromDescription(action, datasetDesc) ; + } + + /** + * Construct a Dataset based on a dataset description. + */ + + protected Dataset datasetFromDescription(HttpAction action, DatasetDescription datasetDesc) + { + try { + if ( datasetDesc == null ) + return null ; + if ( datasetDesc.isEmpty() ) + return null ; + + List<String> graphURLs = datasetDesc.getDefaultGraphURIs() ; + List<String> namedGraphs = datasetDesc.getNamedGraphURIs() ; + + if ( graphURLs.size() == 0 && namedGraphs.size() == 0 ) + return null ; + + Dataset dataset = DatasetFactory.createMem() ; + // Look in cache for loaded graphs!! + + // ---- Default graph + { + Model model = ModelFactory.createDefaultModel() ; + for ( String uri : graphURLs ) + { + if ( uri == null || uri.equals("") ) + throw new InternalErrorException("Default graph URI is null or the empty string") ; + + try { + GraphLoadUtils.loadModel(model, uri, MaxTriples) ; + action.log.info(format("[%d] Load (default graph) %s", action.id, uri)) ; + } catch (RiotException ex) { + action.log.info(format("[%d] Parsing error loading %s: %s", action.id, uri, ex.getMessage())) ; + ServletOps.errorBadRequest("Failed to load URL (parse error) "+uri+" : "+ex.getMessage()) ; + } catch (Exception ex) + { + action.log.info(format("[%d] Failed to load (default) %s: %s", action.id, uri, ex.getMessage())) ; + ServletOps.errorBadRequest("Failed to load URL "+uri) ; + } + } + dataset.setDefaultModel(model) ; + } + // ---- Named graphs + if ( namedGraphs != null ) + { + for ( String uri : namedGraphs ) + { + if ( uri == null || uri.equals("") ) + throw new InternalErrorException("Named graph URI is null or the empty string") ; + + try { + Model model = ModelFactory.createDefaultModel() ; + GraphLoadUtils.loadModel(model, uri, MaxTriples) ; + action.log.info(format("[%d] Load (named graph) %s", action.id, uri)) ; + dataset.addNamedModel(uri, model) ; + } catch (RiotException ex) { + action.log.info(format("[%d] Parsing error loading %s: %s", action.id, uri, ex.getMessage())) ; + ServletOps.errorBadRequest("Failed to load URL (parse error) "+uri+" : "+ex.getMessage()) ; + } catch (Exception ex) + { + action.log.info(format("[%d] Failed to load (named graph) %s: %s", action.id, uri, ex.getMessage())) ; + ServletOps.errorBadRequest("Failed to load URL "+uri) ; + } + } + } + + return dataset ; + + } + catch (ActionErrorException ex) { throw ex ; } + catch (Exception ex) + { + action.log.info(format("[%d] SPARQL parameter error: "+ex.getMessage(),action.id, ex)) ; + ServletOps.errorBadRequest("Parameter error: "+ex.getMessage()); + return null ; + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java new file mode 100644 index 0000000..2adeef3 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java @@ -0,0 +1,358 @@ +/** + * 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 static org.apache.jena.riot.WebContent.contentTypeSPARQLQuery ; +import static org.apache.jena.riot.WebContent.contentTypeSPARQLUpdate ; + +import java.util.List ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.fuseki.DEF ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.fuseki.server.DataAccessPoint ; +import org.apache.jena.fuseki.server.DataService ; +import org.apache.jena.fuseki.server.Endpoint ; +import org.apache.jena.fuseki.server.OperationName ; +import org.apache.jena.riot.web.HttpNames ; + +/** This servlet can be attached to a dataset location + * and acts as a router for all SPARQL operations + * (query, update, graph store, both direct and indirect naming). + */ +public abstract class SPARQL_UberServlet extends ActionSPARQL +{ + protected abstract boolean allowQuery(HttpAction action) ; + protected abstract boolean allowUpdate(HttpAction action) ; + protected abstract boolean allowREST_R(HttpAction action) ; + protected abstract boolean allowREST_W(HttpAction action) ; + protected abstract boolean allowQuadsR(HttpAction action) ; + protected abstract boolean allowQuadsW(HttpAction action) ; + + public static class ReadOnly extends SPARQL_UberServlet + { + public ReadOnly() { super() ; } + @Override protected boolean allowQuery(HttpAction action) { return true ; } + @Override protected boolean allowUpdate(HttpAction action) { return false ; } + @Override protected boolean allowREST_R(HttpAction action) { return true ; } + @Override protected boolean allowREST_W(HttpAction action) { return false ; } + @Override protected boolean allowQuadsR(HttpAction action) { return true ; } + @Override protected boolean allowQuadsW(HttpAction action) { return false ; } + } + + public static class ReadWrite extends SPARQL_UberServlet + { + public ReadWrite() { super() ; } + @Override protected boolean allowQuery(HttpAction action) { return true ; } + @Override protected boolean allowUpdate(HttpAction action) { return true ; } + @Override protected boolean allowREST_R(HttpAction action) { return true ; } + @Override protected boolean allowREST_W(HttpAction action) { return true ; } + @Override protected boolean allowQuadsR(HttpAction action) { return true ; } + @Override protected boolean allowQuadsW(HttpAction action) { return true ; } + } + + public static class AccessByConfig extends SPARQL_UberServlet + { + public AccessByConfig() { super() ; } + @Override protected boolean allowQuery(HttpAction action) { return isEnabled(action, OperationName.Query) ; } + @Override protected boolean allowUpdate(HttpAction action) { return isEnabled(action, OperationName.Update) ; } + @Override protected boolean allowREST_R(HttpAction action) { return isEnabled(action, OperationName.GSP_R) || isEnabled(action, OperationName.GSP) ; } + @Override protected boolean allowREST_W(HttpAction action) { return isEnabled(action, OperationName.GSP) ; } + // Quad operations tied to presence/absence of GSP. + @Override protected boolean allowQuadsR(HttpAction action) { return isEnabled(action, OperationName.GSP_R) ; } + @Override protected boolean allowQuadsW(HttpAction action) { return isEnabled(action, OperationName.GSP) ; } + + private boolean isEnabled(HttpAction action, OperationName opName) + { Endpoint operation = action.getEndpoint() ; + + return operation != null && + operation.getOperationName() == opName && + // XXX Active + operation.endpointName != null ; + } + } + + /* This can be used for a single servlet for everything (über-servlet) + * + * It can check for a request that looks like a service request and passes it on. + * This takes precedence over direct naming. + */ + + // Refactor? Extract the direct naming handling. + // To test: enable in SPARQLServer.configureOneDataset + + private final ActionSPARQL queryServlet = new SPARQL_QueryDataset() ; + private final ActionSPARQL updateServlet = new SPARQL_Update() ; + private final ActionSPARQL uploadServlet = new SPARQL_Upload() ; + private final ActionSPARQL gspServlet_R = new SPARQL_GSP_R() ; + private final ActionSPARQL gspServlet_RW = new SPARQL_GSP_RW() ; + private final ActionSPARQL restQuads_R = new REST_Quads_R() ; + private final ActionSPARQL restQuads_RW = new REST_Quads_RW() ; + + public SPARQL_UberServlet() { super(); } + + private String getEPName(String dsname, List<String> endpoints) { + if (endpoints == null || endpoints.size() == 0) + return null ; + String x = endpoints.get(0) ; + if ( ! dsname.endsWith("/") ) + x = dsname+"/"+x ; + else + x = dsname+x ; + return x ; + } + + // These calls should not happen because we hook in at executeAction + @Override protected void validate(HttpAction action) { throw new FusekiException("Call to SPARQL_UberServlet.validate") ; } + @Override protected void perform(HttpAction action) { throw new FusekiException("Call to SPARQL_UberServlet.perform") ; } + + /** Map request to uri in the registry. + * null means no mapping done + */ + @Override + protected String mapRequestToDataset(HttpAction action) { + String uri = ActionLib.removeContextPath(action) ; + return ActionLib.mapRequestToDatasetLongest$(uri) ; + } + + /** Intercept the processing cycle at the point where the action has been set up, + * the dataset target decided but no validation or execution has been done, + * nor any stats have been done. + */ + @Override + protected void executeAction(HttpAction action) { + long id = action.id ; + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + String actionURI = action.getActionURI() ; // No context path + String method = request.getMethod() ; + + DataAccessPoint desc = action.getDataAccessPoint() ; + DataService dSrv = action.getDataService() ; + +// if ( ! dSrv.isActive() ) +// ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, "Dataset not currently active"); + + String trailing = findTrailing(actionURI, desc.getName()) ; + String qs = request.getQueryString() ; + + boolean hasParams = request.getParameterMap().size() > 0 ; + + // Test for parameters - includes HTML forms. + boolean hasParamQuery = request.getParameter(HttpNames.paramQuery) != null ; + // Include old name "request=" + boolean hasParamUpdate = request.getParameter(HttpNames.paramUpdate) != null || request.getParameter(HttpNames.paramRequest) != null ; + boolean hasParamGraph = request.getParameter(HttpNames.paramGraph) != null ; + boolean hasParamGraphDefault = request.getParameter(HttpNames.paramGraphDefault) != null ; + + String ct = request.getContentType() ; + String charset = request.getCharacterEncoding() ; + + MediaType mt = null ; + if ( ct != null ) + mt = MediaType.create(ct, charset) ; + + if (action.log.isInfoEnabled() ) { + //String cxt = action.getContextPath() ; + action.log.info(format("[%d] %s %s :: '%s' :: %s ? %s", id, method, desc.getName(), trailing, (mt==null?"<none>":mt), (qs==null?"":qs))) ; + } + + boolean hasTrailing = ( trailing.length() != 0 ) ; + + if ( !hasTrailing && !hasParams ) { + // Check enabled. But no trailing here. + // if ( serviceDispatch(action, desc.readWriteGraphStore, trailing, restQuads_RW) ) return ; + // if ( serviceDispatch(action, desc.readGraphStore, trailing, restQuads_R) ) return ; + restQuads_RW.executeLifecycle(action) ; + return ; + } + + if ( !hasTrailing ) { + if ( ct != null ) { + if ( hasParamQuery || contentTypeSPARQLQuery.equalsIgnoreCase(ct) ) { + // SPARQL Query + if ( !allowQuery(action) ) + ServletOps.errorForbidden("Forbidden: SPARQL query") ; + executeRequest(action, queryServlet) ; + return ; + } + + if ( hasParamUpdate || contentTypeSPARQLUpdate.equalsIgnoreCase(ct) ) { + // SPARQL Update + if ( !allowQuery(action) ) + ServletOps.errorForbidden("Forbidden: SPARQL query") ; + executeRequest(action, updateServlet) ; + return ; + } + } + + if ( hasParamGraph || hasParamGraphDefault ) { + doGraphStoreProtocol(action) ; + return ; + } + + ServletOps.errorBadRequest("Malformed request") ; + ServletOps.errorForbidden("Forbidden: SPARQL Graph Store Protocol : Read operation : "+method) ; + } + + final boolean checkForPossibleService = true ; + if ( checkForPossibleService ) { + // There is a trailing part. + // Check it's not the same name as a registered service. + // If so, dispatch to that service. + + // action.operation == query etc. + + if ( serviceDispatch(action, OperationName.Query, queryServlet) ) return ; + if ( serviceDispatch(action, OperationName.Update, updateServlet) ) return ; + if ( serviceDispatch(action, OperationName.Upload, uploadServlet) ) return ; + if ( hasParams ) { + if ( serviceDispatch(action, OperationName.GSP_R, gspServlet_R) ) return ; + if ( serviceDispatch(action, OperationName.GSP, gspServlet_RW) ) return ; + } else { + // No parameters - do as a quads operation on the dataset. + if ( serviceDispatch(action, OperationName.GSP_R, restQuads_R) ) return ; + if ( serviceDispatch(action, OperationName.GSP, restQuads_RW) ) return ; + } + + // If no params, its a daatset operation. + } + // There is a trailing part - params are illegal by this point. + if ( hasParams ) + // ?? Revisit to include query-on-one-graph + //errorBadRequest("Can't invoke a query-string service on a direct named graph") ; + ServletOps.errorNotFound("Not found: dataset='"+printName(desc.getName())+"' service='"+printName(trailing)+"'"); + + // There is a trailing part - not a service, no params ==> GSP direct naming. + doGraphStoreProtocol(action) ; + } + + private String printName(String x) { + if ( x.startsWith("/") ) + return x.substring(1) ; + return x ; + } + + private void doGraphStoreProtocol(HttpAction action) { + // The GSP servlets handle direct and indirect naming. + Endpoint operation = action.getEndpoint() ; + String method = action.request.getMethod() ; + + if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || + HttpNames.METHOD_HEAD.equalsIgnoreCase(method) ) + { + if ( ! allowREST_R(action)) + // Graphs Store Protocol, indirect naming, read + // Indirect naming. Prefer the R service if available. + if ( OperationName.GSP_R == operation.getOperationName() ) + executeRequest(action, gspServlet_R) ; + else if ( OperationName.GSP == operation.getOperationName() ) + executeRequest(action, gspServlet_RW) ; + else + ServletOps.errorMethodNotAllowed(method) ; + return ; + } + + // Graphs Store Protocol, indirect naming, write + if ( ! allowREST_W(action)) + ServletOps.errorForbidden("Forbidden: SPARQL Graph Store Protocol : Write operation : "+method) ; + executeRequest(action, gspServlet_RW) ; + return ; + } + + private void executeRequest(HttpAction action, ActionSPARQL servlet) { + servlet.executeLifecycle(action) ; +// // Forwarded dispatch. +// try +// { +// String target = getEPName(desc.name, endpointList) ; +// if ( target == null ) +// errorMethodNotAllowed(request.getMethod()) ; +// // ** relative servlet forward +// request.getRequestDispatcher(target).forward(request, response) ; + + +// // ** absolute srvlet forward +// // getServletContext().getRequestDispatcher(target) ; +// } catch (Exception e) { errorOccurred(e) ; } + } + + protected static MediaType contentNegotationQuads(HttpAction action) { + MediaType mt = ConNeg.chooseContentType(action.request, DEF.quadsOffer, DEF.acceptNQuads) ; + if ( mt == null ) + return null ; + if ( mt.getContentType() != null ) + action.response.setContentType(mt.getContentType()); + if ( mt.getCharset() != null ) + action.response.setCharacterEncoding(mt.getCharset()) ; + return mt ; + } + + /** return true if dispatched + * @param opName + */ + private boolean serviceDispatch(HttpAction action, OperationName opName, ActionSPARQL servlet) { + Endpoint operation = action.getEndpoint() ; + if ( operation == null ) { + Fuseki.serverLog.warn("No operation: "+opName, new Throwable()) ; + return false ; + } + if ( ! operation.isType(opName) ) + return false ; + servlet.executeLifecycle(action) ; + return true ; + } + + /** Find part after the dataset name: service name or the graph (direct naming) */ + protected String findTrailing(String uri, String dsname) { + if ( dsname.length() >= uri.length() ) + return "" ; + return uri.substring(dsname.length()+1) ; // Skip the separating "/" + } + + @Override + protected void doHead(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doPut(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doDelete(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java new file mode 100644 index 0000000..f2543e7 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java @@ -0,0 +1,304 @@ +/* + * 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 static org.apache.jena.fuseki.server.CounterName.UpdateExecErrors ; +import static org.apache.jena.riot.WebContent.charsetUTF8 ; +import static org.apache.jena.riot.WebContent.contentTypeHTMLForm ; +import static org.apache.jena.riot.WebContent.contentTypeSPARQLUpdate ; +import static org.apache.jena.riot.WebContent.ctSPARQLUpdate ; +import static org.apache.jena.riot.WebContent.isHtmlForm ; +import static org.apache.jena.riot.WebContent.matchContentType ; +import static org.apache.jena.riot.web.HttpNames.paramRequest ; +import static org.apache.jena.riot.web.HttpNames.paramUpdate ; +import static org.apache.jena.riot.web.HttpNames.paramUsingGraphURI ; +import static org.apache.jena.riot.web.HttpNames.paramUsingNamedGraphURI ; + +import java.io.ByteArrayInputStream ; +import java.io.IOException ; +import java.io.InputStream ; +import java.util.Arrays ; +import java.util.Collection ; +import java.util.Enumeration ; +import java.util.List ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.atlas.lib.StrUtils ; +import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.iri.IRI ; +import org.apache.jena.riot.system.IRIResolver ; +import org.apache.jena.riot.web.HttpNames ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.graph.Node ; +import com.hp.hpl.jena.graph.NodeFactory ; +import com.hp.hpl.jena.query.QueryParseException ; +import com.hp.hpl.jena.query.Syntax ; +import com.hp.hpl.jena.sparql.modify.UsingList ; +import com.hp.hpl.jena.update.UpdateAction ; +import com.hp.hpl.jena.update.UpdateException ; +import com.hp.hpl.jena.update.UpdateFactory ; +import com.hp.hpl.jena.update.UpdateRequest ; + +public class SPARQL_Update extends SPARQL_Protocol +{ + // Base URI used to isolate parsing from the current directory of the server. + private static final String UpdateParseBase = Fuseki.BaseParserSPARQL ; + private static final IRIResolver resolver = IRIResolver.create(UpdateParseBase) ; + + public SPARQL_Update() + { super() ; } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(HttpSC.BAD_REQUEST_400, "Attempt to perform SPARQL update by GET. Use POST") ; + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doCommon(request, response) ; + } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + { + response.setHeader(HttpNames.hAllow, "OPTIONS,POST"); + response.setHeader(HttpNames.hContentLengh, "0") ; + } + + @Override + protected void perform(HttpAction action) + { + ContentType ct = FusekiLib.getContentType(action) ; + if ( ct == null ) + ct = ctSPARQLUpdate ; + + if ( matchContentType(ctSPARQLUpdate, ct) ) { + executeBody(action) ; + return ; + } + if ( isHtmlForm(ct) ) { + executeForm(action) ; + return ; + } + ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Bad content type: " + action.request.getContentType()) ; + } + + protected static List<String> paramsForm = Arrays.asList(paramRequest, paramUpdate, + paramUsingGraphURI, paramUsingNamedGraphURI) ; + protected static List<String> paramsPOST = Arrays.asList(paramUsingGraphURI, paramUsingNamedGraphURI) ; + + @Override + protected void validate(HttpAction action) + { + HttpServletRequest request = action.request ; + + if ( ! HttpNames.METHOD_POST.equalsIgnoreCase(request.getMethod()) ) + ServletOps.errorMethodNotAllowed("SPARQL Update : use POST") ; + + ContentType ct = FusekiLib.getContentType(action) ; + if ( ct == null ) + ct = ctSPARQLUpdate ; + // ---- + + if ( matchContentType(ctSPARQLUpdate, ct) ) + { + String charset = request.getCharacterEncoding() ; + if ( charset != null && ! charset.equalsIgnoreCase(charsetUTF8) ) + ServletOps.errorBadRequest("Bad charset: "+charset) ; + validate(action, paramsPOST) ; + return ; + } + + if ( isHtmlForm(ct) ) + { + int x = countParamOccurences(request, paramUpdate) + countParamOccurences(request, paramRequest) ; + if ( x == 0 ) + ServletOps.errorBadRequest("SPARQL Update: No 'update=' parameter") ; + if ( x != 1 ) + ServletOps.errorBadRequest("SPARQL Update: Multiple 'update=' parameters") ; + + String requestStr = request.getParameter(paramUpdate) ; + if ( requestStr == null ) + requestStr = request.getParameter(paramRequest) ; + if ( requestStr == null ) + ServletOps.errorBadRequest("SPARQL Update: No update= in HTML form") ; + validate(action, paramsForm) ; + return ; + } + + ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Must be "+contentTypeSPARQLUpdate+" or "+contentTypeHTMLForm+" (got "+ct.getContentType()+")") ; + } + + protected void validate(HttpAction action, Collection<String> params) + { + if ( params != null ) + { + Enumeration<String> en = action.request.getParameterNames() ; + for ( ; en.hasMoreElements() ; ) + { + String name = en.nextElement() ; + if ( ! params.contains(name) ) + ServletOps.warning(action, "SPARQL Update: Unrecognize request parameter (ignored): "+name) ; + } + } + } + + private void executeBody(HttpAction action) + { + InputStream input = null ; + try { input = action.request.getInputStream() ; } + catch (IOException ex) { ServletOps.errorOccurred(ex) ; } + + if ( action.verbose ) + { + // Verbose mode only .... capture request for logging (does not scale). + String requestStr = null ; + try { requestStr = IO.readWholeFileAsUTF8(input) ; } + catch (IOException ex) { IO.exception(ex) ; } + action.log.info(format("[%d] Update = %s", action.id, ServletOps.formatForLog(requestStr))) ; + + input = new ByteArrayInputStream(requestStr.getBytes()); + requestStr = null; + } + + execute(action, input) ; + ServletOps.successNoContent(action) ; + } + + private void executeForm(HttpAction action) + { + String requestStr = action.request.getParameter(paramUpdate) ; + if ( requestStr == null ) + requestStr = action.request.getParameter(paramRequest) ; + + if ( action.verbose ) + action.log.info(format("[%d] Form update = \n%s", action.id, requestStr)) ; + // A little ugly because we are taking a copy of the string, but hopefully shouldn't be too big if we are in this code-path + // If we didn't want this additional copy, we could make the parser take a Reader in addition to an InputStream + byte[] b = StrUtils.asUTF8bytes(requestStr) ; + ByteArrayInputStream input = new ByteArrayInputStream(b); + requestStr = null; // free it early at least + execute(action, input); + ServletOps.successPage(action,"Update succeeded") ; + } + + private void execute(HttpAction action, InputStream input) + { + UsingList usingList = processProtocol(action.request) ; + + // If the dsg is transactional, then we can parse and execute the update in a streaming fashion. + // If it isn't, we need to read the entire update request before performing any updates, because + // we have to attempt to make the request atomic in the face of malformed queries + UpdateRequest req = null ; + if (!action.isTransactional()) + { + try { + // TODO implement a spill-to-disk version of this + req = UpdateFactory.read(usingList, input, UpdateParseBase, Syntax.syntaxARQ); + } + catch (UpdateException ex) { ServletOps.errorBadRequest(ex.getMessage()) ; return ; } + catch (QueryParseException ex) { ServletOps.errorBadRequest(messageForQPE(ex)) ; return ; } + } + + action.beginWrite() ; + try { + if (req == null ) + UpdateAction.parseExecute(usingList, action.getActiveDSG(), input, UpdateParseBase, Syntax.syntaxARQ); + else + UpdateAction.execute(req, action.getActiveDSG()) ; + action.commit() ; + } catch (UpdateException ex) { + action.abort() ; + incCounter(action.getEndpoint().getCounters(), UpdateExecErrors) ; + ServletOps.errorOccurred(ex.getMessage()) ; + } catch (QueryParseException ex) { + action.abort() ; + // Counter inc'ed further out. + ServletOps.errorBadRequest(messageForQPE(ex)) ; + } catch (Throwable ex) { + if ( ! ( ex instanceof ActionErrorException ) ) + { + try { action.abort() ; } catch (Exception ex2) {} + ServletOps.errorOccurred(ex.getMessage(), ex) ; + } + } finally { action.endWrite(); } + } + + /* [It is an error to supply the using-graph-uri or using-named-graph-uri parameters + * when using this protocol to convey a SPARQL 1.1 Update request that contains an + * operation that uses the USING, USING NAMED, or WITH clause.] + * + * We will simply capture any using parameters here and pass them to the parser, which will be + * responsible for throwing an UpdateException if the query violates the above requirement, + * and will also be responsible for adding the using parameters to update queries that can + * accept them. + */ + private UsingList processProtocol(HttpServletRequest request) + { + UsingList toReturn = new UsingList(); + + String[] usingArgs = request.getParameterValues(paramUsingGraphURI) ; + String[] usingNamedArgs = request.getParameterValues(paramUsingNamedGraphURI) ; + if ( usingArgs == null && usingNamedArgs == null ) + return toReturn; + if ( usingArgs == null ) + usingArgs = new String[0] ; + if ( usingNamedArgs == null ) + usingNamedArgs = new String[0] ; + // Impossible. +// if ( usingArgs.length == 0 && usingNamedArgs.length == 0 ) +// return ; + + for (String nodeUri : usingArgs) + { + toReturn.addUsing(createNode(nodeUri)); + } + for (String nodeUri : usingNamedArgs) + { + toReturn.addUsingNamed(createNode(nodeUri)); + } + + return toReturn; + } + + private static Node createNode(String x) + { + try { + IRI iri = resolver.resolve(x) ; + return NodeFactory.createURI(iri.toString()) ; + } catch (Exception ex) + { + ServletOps.errorBadRequest("SPARQL Update: bad IRI: "+x) ; + return null ; + } + + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java new file mode 100644 index 0000000..699d06a --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java @@ -0,0 +1,289 @@ +/* + * 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 java.io.IOException ; +import java.io.InputStream ; +import java.io.PrintWriter ; +import java.util.zip.GZIPInputStream ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +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.web.ContentType ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.iri.IRI ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFLanguages ; +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.web.HttpSC ; + +import com.hp.hpl.jena.graph.Node ; +import com.hp.hpl.jena.graph.NodeFactory ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ; +import com.hp.hpl.jena.sparql.core.Quad ; + +public class SPARQL_Upload extends ActionSPARQL +{ + public SPARQL_Upload() { + super() ; + } + + // Methods to respond to. + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doCommon(request, response) ; + } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + { + response.setHeader(HttpNames.hAllow, "OPTIONS,POST"); + response.setHeader(HttpNames.hContentLengh, "0") ; + } + + @Override + protected void perform(HttpAction action) + { + // Only allows one file in the upload. + boolean isMultipart = ServletFileUpload.isMultipartContent(action.request); + if ( ! isMultipart ) + ServletOps.error(HttpSC.BAD_REQUEST_400 , "Not a file upload") ; + + long count = upload(action, Fuseki.BaseUpload) ; + ServletOps.success(action) ; + try { + action.response.setContentType("text/html") ; + action.response.setStatus(HttpSC.OK_200); + PrintWriter out = action.response.getWriter() ; + out.println("<html>") ; + out.println("<head>") ; + out.println("</head>") ; + out.println("<body>") ; + out.println("<h1>Success</h1>"); + out.println("<p>") ; + out.println("Triples = "+count + "\n"); + out.println("<p>") ; + out.println("</p>") ; + out.println("<button onclick=\"timeFunction()\">Back to Fuseki</button>"); + out.println("</p>") ; + out.println("<script type=\"text/javascript\">"); + out.println("function timeFunction(){"); + out.println("window.location.href = \"/fuseki.html\";}"); + out.println("</script>"); + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + ServletOps.success(action) ; + } + catch (Exception ex) { ServletOps.errorOccurred(ex) ; } + } + + // Also used by SPARQL_REST + static public long upload(HttpAction action, String base) + { + if ( action.isTransactional() ) + return uploadTxn(action, base) ; + else + return uploadNonTxn(action, base) ; + } + + /** Non-transaction - buffer to a temporary graph so that parse errors + * are caught before inserting any data. + */ + private static long uploadNonTxn(HttpAction action, String base) { + UploadDetails upload = uploadWorker(action, base) ; + String graphName = upload.graphName ; + DatasetGraph dataTmp = upload.data ; + long count = upload.count ; + + if ( graphName == null ) + action.log.info(format("[%d] Upload: %d Quads(s)",action.id, count)) ; + else + action.log.info(format("[%d] Upload: Graph: %s, %d triple(s)", action.id, graphName, count)) ; + + Node gn = null ; + if ( graphName != null ) { + gn = graphName.equals(HttpNames.valueDefault) + ? Quad.defaultGraphNodeGenerated + : NodeFactory.createURI(graphName) ; + } + + action.beginWrite() ; + try { + if ( gn != null ) + FusekiLib.addDataInto(dataTmp.getDefaultGraph(), action.getActiveDSG(), gn) ; + else + FusekiLib.addDataInto(dataTmp, action.getActiveDSG()) ; + + action.commit() ; + return count ; + } catch (RuntimeException ex) + { + // If anything went wrong, try to backout. + try { action.abort() ; } catch (Exception ex2) {} + ServletOps.errorOccurred(ex.getMessage()) ; + return -1 ; + } + finally { action.endWrite() ; } + } + + /** Transactional - we'd like data to go straight to the destination, with an abort on parse error. + * But file upload with a name means that the name can be after the data + * (it is in the Fuseki default pages). + * Use Graph Store protocol for bulk uploads. + * (It would be possible to process the incoming stream and see the graph name first.) + */ + private static long uploadTxn(HttpAction action, String base) { + // We can't do better than the non-transaction approach. + return uploadNonTxn(action, base) ; + } + + static class UploadDetails { + final String graphName ; + final DatasetGraph data ; + final long count ; + UploadDetails(String gn, DatasetGraph dsg, long parserCount) { + this.graphName = gn ; + this.data = dsg ; + this.count = parserCount ; + } + } + + /** Process an HTTP file upload of RDF with additiona name field for the graph name. + * We can't stream straight into a dataset because the graph name can be after the data. + * @return graph name and count + */ + + // ?? Combine with Upload.fileUploadWorker + // Difference is the handling of names for graphs. + static private UploadDetails uploadWorker(HttpAction action, String base) + { + DatasetGraph dsgTmp = DatasetGraphFactory.createMem() ; + 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) ) + { + 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) ; + } + } + } + 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.getContentType()) ; + 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.getContentType(), ct.getCharset(), lang.getName())) ; + + StreamRDF x = StreamRDFLib.dataset(dsgTmp) ; + StreamRDFCounting dest = StreamRDFLib.count(x) ; + ActionSPARQL.parse(action, dest, stream, lang, base); + count = dest.count() ; + } + } + + if ( graphName == null || graphName.equals("") ) + graphName = HttpNames.valueDefault ; + if ( isQuads ) + graphName = null ; + return new UploadDetails(graphName, dsgTmp, count) ; + } + catch (ActionErrorException ex) { throw ex ; } + catch (Exception ex) { ServletOps.errorOccurred(ex) ; return null ; } + } + + @Override + protected void validate(HttpAction action) + {} +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java new file mode 100644 index 0000000..4ac30be --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java @@ -0,0 +1,89 @@ +/* + * 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 java.util.concurrent.atomic.AtomicLong ; + +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.lib.StrUtils ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.riot.web.HttpNames ; + +/** + * Addition HTTP Servlet operations. + */ +public abstract class ServletBase extends HttpServlet { + public static final String METHOD_DELETE = "DELETE" ; + public static final String METHOD_HEAD = "HEAD" ; + public static final String METHOD_GET = "GET" ; + public static final String METHOD_OPTIONS = "OPTIONS" ; + public static final String METHOD_POST = "POST" ; + public static final String METHOD_PUT = "PUT" ; + public static final String METHOD_TRACE = "TRACE" ; + public static final String METHOD_PATCH = "PATCH" ; + + private static AtomicLong requestIdAlloc = new AtomicLong(0) ; + + protected ServletBase() {} + + /** + * Helper method which gets a unique request ID and appends it as a header + * to the response + * + * @param request + * HTTP Request + * @param response + * HTTP Response + * @return Request ID + */ + protected static long allocRequestId(HttpServletRequest request, HttpServletResponse response) { + long id = requestIdAlloc.incrementAndGet() ; + addRequestId(response, id) ; + return id ; + } + + /** + * Helper method for attaching a request ID to a response as a header + * + * @param response + * Response + * @param id + * Request ID + */ + protected static void addRequestId(HttpServletResponse response, long id) { + response.addHeader("Fuseki-Request-ID", Long.toString(id)) ; + } + + static final String varyHeaderSetting = StrUtils.strjoin(",", + HttpNames.hAccept, + HttpNames.hAcceptEncoding, + HttpNames.hAcceptCharset) ; + + public static void setVaryHeader(HttpServletResponse httpResponse) { + httpResponse.setHeader(HttpNames.hVary, varyHeaderSetting) ; + } + + public static void setCommonHeaders(HttpServletResponse httpResponse) { + httpResponse.setHeader(HttpNames.hAccessControlAllowOrigin, "*") ; + httpResponse.setHeader(HttpNames.hServer, Fuseki.serverHttpName) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java new file mode 100644 index 0000000..277bf47 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java @@ -0,0 +1,209 @@ +/** + * 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 java.io.IOException ; +import java.io.PrintWriter ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JSON ; +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.servlets.UploadDetails.PreState ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.web.HttpNames ; +import org.apache.jena.web.HttpSC ; + +public class ServletOps { + + public static void responseSendError(HttpServletResponse response, int statusCode, String message) { + try { + response.sendError(statusCode, message) ; + } catch (IOException ex) { + errorOccurred(ex) ; + } catch (IllegalStateException ex) {} + } + + public static void responseSendError(HttpServletResponse response, int statusCode) { + try { + response.sendError(statusCode) ; + } catch (IOException ex) { + errorOccurred(ex) ; + } + } + + public static void successNoContent(HttpAction action) { + success(action, HttpSC.NO_CONTENT_204) ; + } + + public static void success(HttpAction action) { + success(action, HttpSC.OK_200) ; + } + + public static void successCreated(HttpAction action) { + success(action, HttpSC.CREATED_201) ; + } + + // When 404 is no big deal e.g. HEAD + public static void successNotFound(HttpAction action) { + success(action, HttpSC.NOT_FOUND_404) ; + } + + // + public static void success(HttpAction action, int httpStatusCode) { + action.response.setStatus(httpStatusCode) ; + } + + public static void successPage(HttpAction action, String message) { + try { + action.response.setContentType("text/html") ; + action.response.setStatus(HttpSC.OK_200) ; + PrintWriter out = action.response.getWriter() ; + out.println("<html>") ; + out.println("<head>") ; + out.println("</head>") ; + out.println("<body>") ; + out.println("<h1>Success</h1>") ; + if ( message != null ) { + out.println("<p>") ; + out.println(message) ; + out.println("</p>") ; + } + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + } catch (IOException ex) { + errorOccurred(ex) ; + } + } + + public static void warning(HttpAction action, String string) { + action.log.warn(string) ; + } + + public static void warning(HttpAction action, String string, Throwable thorwable) { + action.log.warn(string, thorwable) ; + } + + public static void errorBadRequest(String string) { + error(HttpSC.BAD_REQUEST_400, string) ; + } + + public static void errorNotFound(String string) { + error(HttpSC.NOT_FOUND_404, string) ; + } + + public static void errorNotImplemented(String msg) { + error(HttpSC.NOT_IMPLEMENTED_501, msg) ; + } + + public static void errorMethodNotAllowed(String method) { + errorMethodNotAllowed(method, "HTTP method not allowed: " + method) ; + } + + public static void errorMethodNotAllowed(String method, String msg) { + error(HttpSC.METHOD_NOT_ALLOWED_405, msg) ; + } + + public static void errorForbidden() { + error(HttpSC.FORBIDDEN_403, "Forbidden") ; + } + + public static void errorForbidden(String msg) { + if ( msg != null ) + error(HttpSC.FORBIDDEN_403, msg) ; + else + errorForbidden() ; + } + + public static void error(int statusCode) { + throw new ActionErrorException(null, null, statusCode) ; + } + + public static void error(int statusCode, String string) { + throw new ActionErrorException(null, string, statusCode) ; + } + + public static void errorOccurred(String message) { + errorOccurred(message, null) ; + } + + public static void errorOccurred(Throwable ex) { + errorOccurred(null, ex) ; + } + + public static void errorOccurred(String message, Throwable ex) { + throw new ActionErrorException(ex, message, HttpSC.INTERNAL_SERVER_ERROR_500) ; + } + + public static String formatForLog(String string) { + string = string.replace('\n', ' ') ; + string = string.replace('\r', ' ') ; + return string ; + } + + public static void setNoCache(HttpAction action) { + setNoCache(action.response) ; + } + + public static void setNoCache(HttpServletResponse response) { + response.setHeader(HttpNames.hCacheControl, "must-revalidate,no-cache,no-store"); + response.setHeader(HttpNames.hPragma, "no-cache"); + } + + /** Send a JSON value as a 200 response. Null object means no response body and no content-type headers. */ + public static void sendJsonReponse(HttpAction action, JsonValue v) { + if ( v == null ) { + ServletOps.success(action); + //ServletOps.successNoContent(action); + return ; + } + + ServletOps.success(action); + sendJson(action, v) ; + } + + /** Send a JSON value as a 200 response. Null object means no response body and no content-type headers. */ + public static void sendJson(HttpAction action, JsonValue v) { + if ( v == null ) + return ; + + try { + HttpServletResponse response = action.response ; + ServletOutputStream out = response.getOutputStream() ; + response.setContentType(WebContent.contentTypeJSON); + response.setCharacterEncoding(WebContent.charsetUTF8) ; + JSON.write(out, v) ; + out.println() ; + out.flush() ; + } catch (IOException ex) { ServletOps.errorOccurred(ex) ; } + } + + /** response to a upload operation of some kind. */ + public static void uploadResponse(HttpAction action, UploadDetails details) { + if ( details.getExistedBefore().equals(PreState.ABSENT) ) + ServletOps.successCreated(action) ; + else + ServletOps.success(action) ; // successNoContent if empty body. + JsonValue v = details.detailsJson() ; + ServletOps.sendJson(action, v) ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/Upload.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/Upload.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/Upload.java new file mode 100644 index 0000000..0343f42 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/Upload.java @@ -0,0 +1,165 @@ +/** + * 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 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.FusekiLib ; +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.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFLib ; + +public class Upload { + public static UploadDetails incomingData(HttpAction action, StreamRDF dest) { + ContentType ct = FusekiLib.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.getContentType()) ; + 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) ; } + + int len = action.request.getContentLength() ; + + StreamRDFCounting countingDest = StreamRDFLib.count(dest) ; + try { + ActionSPARQL.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.getContentType(), 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.getContentType(), ct.getCharset(), lang.getName(), + ex.getMessage())) ; + throw ex ; + } + } + + /** Process an HTTP upload of RDF files (triples or quads) + * Stream straight into a graph or dataset -- unlike SPARQL_Upload the destination + * is known at the start of the multipart file body + */ + + public static UploadDetails fileUploadWorker(HttpAction action, StreamRDF dest) { + String base = ActionLib.wholeRequestURL(action.request) ; + ServletFileUpload upload = new ServletFileUpload(); + //log.info(format("[%d] Upload: Field=%s ignored", action.id, fieldName)) ; + // This is teh overal count. + + // Overall counting. + 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") ; + 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.getContentType()) ; + + 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 { + ActionSPARQL.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.getContentType(), 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.getContentType(), 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 ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/UploadDetails.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/UploadDetails.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/UploadDetails.java new file mode 100644 index 0000000..a5c144b --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/UploadDetails.java @@ -0,0 +1,86 @@ +/** + * 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 org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonValue ; + +/** Record of an upload */ +public class UploadDetails { + public enum PreState { EXISTED, ABSENT, UNKNOWN } + + private final long count ; + private final long tripleCount ; + private final long quadCount ; + private PreState state = PreState.UNKNOWN ; + + /*package*/ UploadDetails(long parserCount, long parserTripleCount, long parserQuadCount) { + this.count = parserCount ; + this.tripleCount = parserTripleCount ; + this.quadCount = parserQuadCount ; + } + + public static String detailsStr(long count, long tripleCount, long quadCount) { + return String.format("Count=%d Triples=%d Quads=%d", count, tripleCount, quadCount) ; + } + + public String detailsStr() { + return detailsStr(count, tripleCount, quadCount) ; + } + + public static String jCount = "count" ; + public static String jTriplesCount = "tripleCount" ; + public static String jQuadsCount = "quadCount" ; + + public static JsonValue detailsJson(long count, long tripleCount, long quadCount) { + JsonBuilder b = new JsonBuilder() ; + b.startObject("details") ; + b.key(jCount).value(count) ; + b.key(jTriplesCount).value(tripleCount) ; + b.key(jQuadsCount).value(quadCount) ; + b.finishObject("details") ; + return b.build() ; + } + + public JsonValue detailsJson() { + return detailsJson(count, tripleCount, quadCount) ; + } + + public long getCount() { + return count ; + } + + public long getTripleCount() { + return tripleCount ; + } + + public long getQuadCount() { + return quadCount ; + } + + public void setExistedBefore(boolean existedBefore) { + if ( existedBefore ) + setExistedBefore(PreState.EXISTED) ; + else + setExistedBefore(PreState.ABSENT) ; + } + public void setExistedBefore(PreState state) { this.state = state ; } + + public PreState getExistedBefore() { return state ; } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/DataValidator.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/DataValidator.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/DataValidator.java new file mode 100644 index 0000000..055d798 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/DataValidator.java @@ -0,0 +1,131 @@ +/** + * 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.validation; + +import static org.apache.jena.riot.SysRIOT.fmtMessage ; + +import java.io.StringReader ; +import java.util.ArrayList ; +import java.util.List ; + +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonObject ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.riot.* ; +import org.apache.jena.riot.system.ErrorHandler ; +import org.apache.jena.riot.system.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFLib ; + +public class DataValidator extends ValidatorBaseJson { + public DataValidator() { } + + static final String jInput = "input" ; + + static final String paramFormat = "outputFormat" ; + static final String paramIndirection = "url" ; + static final String paramData = "data" ; + static final String paramSyntax = "languageSyntax" ; + @Override + protected JsonObject execute(ValidationAction action) { + JsonBuilder obj = new JsonBuilder() ; + obj.startObject() ; + + + String syntax = getArgOrNull(action, paramSyntax) ; + if ( syntax == null || syntax.equals("") ) + syntax = RDFLanguages.NQUADS.getName() ; + + Lang language = RDFLanguages.shortnameToLang(syntax) ; + if ( language == null ) { + ServletOps.errorBadRequest("Unknown syntax: " + syntax) ; + return null ; + } + + String string = getArg(action, paramData) ; + StringReader sr = new StringReader(string) ; + obj.key(jInput).value(string) ; + StreamRDF dest = StreamRDFLib.sinkNull() ; + + try { + // Set error handler! + RDFDataMgr.parse(dest, sr, null, language) ; + } catch (RiotParseException ex) { + obj.key(jErrors) ; + + obj.startArray() ; // Errors array + obj.startObject() ; + obj.key(jParseError).value(ex.getMessage()) ; + obj.key(jParseErrorLine).value(ex.getLine()) ; + obj.key(jParseErrorCol).value(ex.getCol()) ; + obj.finishObject() ; + obj.finishArray() ; + + obj.finishObject() ; // Outer object + return obj.build().getAsObject() ; + } catch (RiotException ex) { + obj.key(jErrors) ; + + obj.startArray() ; // Errors array + obj.startObject() ; + obj.key(jParseError).value(ex.getMessage()) ; + obj.finishObject() ; + obj.finishArray() ; + + obj.finishObject() ; // Outer object + return obj.build().getAsObject() ; + } + + + obj.finishObject() ; + return obj.build().getAsObject() ; + } + @Override + protected String validatorName() { + return "RDF Data" ; + } + + // Error handler that records messages + private static class ErrorHandlerMsg implements ErrorHandler + { + private List<String> messages = new ArrayList<>() ; + + ErrorHandlerMsg(List<String> messages) { this.messages = messages; } + + @Override + public void warning(String message, long line, long col) + { output(message, line, col, "Warning", "warning") ; } + + // Attempt to continue. + @Override + public void error(String message, long line, long col) + { output(message, line, col, "Error", "error") ; } + + @Override + public void fatal(String message, long line, long col) + { output(message, line, col, "Fatal", "error") ; throw new RiotException(fmtMessage(message, line, col)) ; } + + private void output(String message, long line, long col, String typeName, String className) + { + String str = fmtMessage(message, line, col) ; + messages.add(str) ; + } + } + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/IRIValidator.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/IRIValidator.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/IRIValidator.java new file mode 100644 index 0000000..3942115 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/validation/IRIValidator.java @@ -0,0 +1,168 @@ +/** + * 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.validation; + +import java.util.ArrayList ; +import java.util.Iterator ; +import java.util.List ; + +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonObject ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.iri.IRI ; +import org.apache.jena.iri.IRIFactory ; +import org.apache.jena.iri.Violation ; +import org.apache.jena.riot.system.IRIResolver ; + +public class IRIValidator extends ValidatorBaseJson { + public IRIValidator() { } + + static IRIFactory iriFactory = IRIResolver.iriFactory ; + + static final String paramIRI = "iri" ; + + // Output is an object { "iris" : [ ] } + // { "iri": "" , "error": [], "warnings": [] } + static final String jIRIs = "iris" ; + static final String jIRI = "iri" ; + + @Override + protected JsonObject execute(ValidationAction action) { + JsonBuilder obj = new JsonBuilder() ; + obj.startObject() ; + + String args[] = getArgs(action, paramIRI) ; + if ( args.length == 0 ) + ServletOps.errorBadRequest("No IRIs supplied"); + + obj.key(jIRIs) ; + obj.startArray() ; + + for ( String iriStr : args ) + { + obj.startObject() ; + obj.key(jIRI).value(iriStr) ; + + IRI iri = iriFactory.create(iriStr) ; + + + List<String> errors = new ArrayList<>() ; + List<String> warnings = new ArrayList<>() ; + + if ( iri.isRelative() ) + warnings.add("Relative IRI: "+iriStr) ; + + Iterator<Violation> vIter = iri.violations(true) ; + for ( ; vIter.hasNext() ; ) + { + Violation v = vIter.next() ; + String str = v.getShortMessage() ; + if ( v.isError() ) + errors.add(str) ; + else + warnings.add(str) ; + } + + obj.key(jErrors) ; + obj.startArray() ; + for ( String msg : errors ) + obj.value(msg) ; + obj.finishArray() ; + + obj.key(jWarnings) ; + obj.startArray() ; + for ( String msg : warnings ) + obj.value(msg) ; + obj.finishArray() ; + + obj.finishObject() ; + } + + + obj.finishArray() ; + + obj.finishObject() ; + return obj.build().getAsObject() ; + } + @Override + protected String validatorName() { + return "RDF Data" ; + } +} + +//static final String paramIRI = "iri" ; +////static IRIFactory iriFactory = IRIFactory.iriImplementation() ; +//static IRIFactory iriFactory = IRIResolver.iriFactory ; +// +//@Override +//protected void execute(HttpServletRequest httpRequest, HttpServletResponse httpResponse) +//{ +// try { +// String[] args = httpRequest.getParameterValues(paramIRI) ; +// ServletOutputStream outStream = httpResponse.getOutputStream() ; +// PrintStream stdout = System.out ; +// PrintStream stderr = System.err ; +// System.setOut(new PrintStream(outStream)) ; +// System.setErr(new PrintStream(outStream)) ; +// +// setHeaders(httpResponse) ; +// +// outStream.println("<html>") ; +// printHead(outStream, "Jena IRI Validator Report") ; +// outStream.println("<body>") ; +// +// outStream.println("<h1>IRI Report</h1>") ; +// +// startFixed(outStream) ; +// +// try { +// boolean first = true ; +// for ( String iriStr : args ) +// { +// if ( ! first ) +// System.out.println() ; +// first = false ; +// +// IRI iri = iriFactory.create(iriStr) ; +// System.out.println(iriStr + " ==> "+iri) ; +// if ( iri.isRelative() ) +// System.out.println("Relative IRI: "+iriStr) ; +// +// Iterator<Violation> vIter = iri.violations(true) ; +// for ( ; vIter.hasNext() ; ) +// { +// String str = vIter.next().getShortMessage() ; +// str = htmlQuote(str) ; +// +// System.out.println(str) ; +// } +// } +// } finally +// { +// finishFixed(outStream) ; +// System.out.flush() ; +// System.err.flush() ; +// System.setOut(stdout) ; +// System.setErr(stdout) ; +// } +// +// outStream.println("</body>") ; +// outStream.println("</html>") ; +// } catch (IOException ex) {} +//}