Skip to content

Commit

Permalink
Merge pull request apache#2538 from karolina-telicent/prefixes-service
Browse files Browse the repository at this point in the history
apacheGH-2543: Prefixes service
  • Loading branch information
rvesse authored Jul 9, 2024
2 parents e68c9dd + 0a4b9a7 commit 565736c
Show file tree
Hide file tree
Showing 34 changed files with 2,144 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,35 @@
import org.apache.jena.sparql.engine.QueryIterator ;

/* Abstraction: QueryStage = PlanElement has a single "build"
* but it's never worng - this two step process here allows for checking
* but it's never wrong - this two step process here allows for checking
*/

/* Can have:
* One arg or list for both subject and object
* (?x) is not the same as ?x
* (?x) is not the same as ?x
*/

public interface PropertyFunction
{
/** Called during query plan construction immediately after the
* construction of the property function instance.
* @param argSubject The parsed argument(s) in the subject position
* @param argSubject The parsed argument(s) in the subject position
* @param predicate The extension URI (as a Node).
* @param argObject The parsed argument(s) in the object position
* @param argObject The parsed argument(s) in the object position
* @param execCxt Execution context
*/
*/
public void build(PropFuncArg argSubject, Node predicate, PropFuncArg argObject, ExecutionContext execCxt) ;


/** Create an iterator of bindings for the given inputs
/** Create an iterator of bindings for the given inputs
* @param input QueryIterator from the previous stage
* @param argSubject The parsed argument(s) in the subject position
* @param argSubject The parsed argument(s) in the subject position
* @param predicate The extension URI (as a Node).
* @param argObject The parsed argument(s) in the object position
* @param argObject The parsed argument(s) in the object position
* @param execCxt The execution context
* @return QueryIterator
*/
public QueryIterator exec(QueryIterator input,
public QueryIterator exec(QueryIterator input,
PropFuncArg argSubject, Node predicate, PropFuncArg argObject,
ExecutionContext execCxt) ;
}
22 changes: 22 additions & 0 deletions jena-fuseki2/examples/config-prefixes.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0

PREFIX : <#>
PREFIX fuseki: <http://jena.apache.org/fuseki#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ja: <http://jena.hpl.hp.com/2005/11/Assembler#>
PREFIX tdb2: <http://jena.apache.org/2016/tdb#>


:service rdf:type fuseki:Service ;
fuseki:name "dataset" ;
fuseki:endpoint [ fuseki:operation fuseki:query ; ] ;
fuseki:endpoint [ fuseki:operation fuseki:update ; ] ;

fuseki:endpoint [ fuseki:operation fuseki:prefixes-r ; fuseki:name "prefixes" ] ;
fuseki:endpoint [ fuseki:operation fuseki:prefixes-ws ; fuseki:name "updatePrefixes" ] ;
fuseki:dataset :dataset ;
.

:dataset rdf:type ja:MemoryDataset ;
ja:data "example-data.trig";
.
6 changes: 6 additions & 0 deletions jena-fuseki2/jena-fuseki-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<!-- micrometer -->
<dependency>
<groupId>io.micrometer</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ public long getRequestsBad() {
/** Cumulative counter of transactions */
public AtomicLong totalTxn = new AtomicLong(0);

/** Note the start of a transaction */
public void startTxn(TxnType mode) {
check(DataServiceStatus.ACTIVE);
activeTxn.getAndIncrement();
totalTxn.getAndIncrement();
}

/** Note the finish of a transaction */
public void finishTxn() {
activeTxn.decrementAndGet();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public class FusekiVocab
public static final Resource opShacl = resource("shacl");
public static final Resource opPatch = resource("patch");

public static final Resource opPREFIXES_R = resource("prefixes-r");
public static final Resource opPREFIXES_RW = resource("prefixes-rw");

// Internal
private static final String stateNameActive = DataServiceStatus.ACTIVE.name;
private static final String stateNameOffline = DataServiceStatus.OFFLINE.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ static private Operation create(Node id, String shortName, String description) {
public static final Operation Patch = alloc(FusekiVocab.opPatch.asNode(), "patch", "RDF Patch");

public static final Operation NoOp = alloc(FusekiVocab.opNoOp.asNode(), "no-op", "No Op");

public static final Operation PREFIXES_R = alloc(FusekiVocab.opPREFIXES_R.asNode(), "prefixes-r", "Read prefixes");
public static final Operation PREFIXES_RW = alloc(FusekiVocab.opPREFIXES_RW.asNode(), "prefixes-rw", "Read-write prefixes");


static {
// Not everyone will remember "_" vs "-" so ...
altName(FusekiVocab.opNoOp_alt, FusekiVocab.opNoOp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class OperationRegistry {
private static final ActionService rdfPatch = new PatchApply();
private static final ActionService noOperation = new NoOpActionService();
private static final ActionService shaclValidation = new SHACL_Validation();
private static final ActionService prefixes_R = new ActionPrefixesR();
private static final ActionService prefixes_RW = new ActionPrefixesRW();

/** The server-wide standard configuration. */
private static final OperationRegistry stdConfig = stdConfig();
Expand All @@ -71,6 +73,9 @@ private static OperationRegistry stdConfig() {
stdOpReg.register(Operation.Shacl, null, shaclValidation);
stdOpReg.register(Operation.Upload, null, uploadServlet);

stdOpReg.register(Operation.PREFIXES_R, null, prefixes_R);
stdOpReg.register(Operation.PREFIXES_RW, null, prefixes_RW);

stdOpReg.register(Operation.NoOp, null, noOperation);
return stdOpReg;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* 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.util.List;
import java.util.Map;
import java.util.Optional;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import org.apache.jena.atlas.logging.FmtLog;
import org.apache.jena.atlas.web.AcceptList;
import org.apache.jena.atlas.web.MediaType;
import org.apache.jena.fuseki.servlets.prefixes.ActionPrefixesBase;
import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils;
import org.apache.jena.fuseki.servlets.prefixes.PrefixesAccess;
import org.apache.jena.fuseki.system.ConNeg;
import org.apache.jena.riot.WebContent;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.web.HttpSC;

public class ActionPrefixesR extends ActionPrefixesBase {

private static final String NO_PREFIX_NS = "";

public ActionPrefixesR() {}

@Override
protected PrefixesAccess prefixes(HttpAction action) {
return ActionPrefixesBase.prefixesFromAction(action);
}

@Override
protected void doOptions(HttpAction action) {
ActionLib.setCommonHeadersForOptions(action);
action.setResponseHeader(HttpNames.hAllow, "GET,OPTIONS");
ServletOps.success(action);
}

enum ResponseTypes {
GET_ALL,
FETCH_URI,
FETCH_PREFIX,
BAD_REQUEST
}

protected ResponseTypes chooseResponseType (String prefix, String uri) {
if (prefix == null && uri == null)
return ResponseTypes.GET_ALL;
else if (prefix != null && uri == null) {
if (prefix.isEmpty()) {
ServletOps.errorBadRequest("Empty prefix!");
return ResponseTypes.BAD_REQUEST;
}
else if (!PrefixUtils.prefixIsValid(prefix)) {
ServletOps.errorBadRequest("Prefix contains illegal characters!");
return ResponseTypes.BAD_REQUEST;
}
else
return ResponseTypes.FETCH_URI;
}
else if (prefix == null && uri != null) {
if (uri.isEmpty()) {
ServletOps.errorBadRequest("Empty URI!");
return ResponseTypes.BAD_REQUEST;
}
else if (!PrefixUtils.uriIsValid(uri)) {
ServletOps.errorBadRequest("URI contains illegal characters!");
return ResponseTypes.BAD_REQUEST;
}
else
return ResponseTypes.FETCH_PREFIX;
}
return ResponseTypes.BAD_REQUEST;
}

@Override
protected void validatePrefixesGET(HttpAction action) {
// Only need to check for presence of expected parameters.
// Values have been checked.
String prefix = action.getRequestParameter(PrefixUtils.PREFIX);
String uri = action.getRequestParameter(PrefixUtils.URI);
if ( prefix != null && uri != null ) {
ServletOps.errorBadRequest("Provide only no paremetrs, or one of the prefix or uri!");
return;
}
}

@Override
protected void doGet(HttpAction action) {
ActionLib.setCommonHeaders(action);
action.beginRead();
try {
String prefix = action.getRequestParameter(PrefixUtils.PREFIX);
String uri = action.getRequestParameter(PrefixUtils.URI);
PrefixesAccess prefixes = prefixes(action);

switch(chooseResponseType(prefix, uri)) {
case GET_ALL -> execGetAll(action, prefixes);
case FETCH_URI -> execFetchURIByPrefix(action, prefixes, prefix);
case FETCH_PREFIX -> execFetchPrefixForURI(action, prefixes, uri);
default -> {
ServletOps.errorBadRequest("Bad request");
return;
}
}
} catch (ActionErrorException ex) {
// pass through
} catch (RuntimeException ex) {
action.abortSilent();
ServletOps.errorOccurred(ex);
} finally {
action.endRead();
}
}

private void execGetAll(HttpAction action, PrefixesAccess prefixes) {
Map<String, String> allPairs = prefixes.getAll();
JsonArray allJsonPairs = new JsonArray();
allPairs.entrySet().stream().forEach(entry -> {
JsonObject jsonObject = jsonObject(entry.getKey(), entry.getValue());
allJsonPairs.add(jsonObject);
FmtLog.debug(action.log, "[%d] Entry: %s: <%s>", action.id, entry.getKey(), entry.getValue());
});
FmtLog.info(action.log, "[%d] - Get all prefix mappings", action.id);
ServletOps.success(action);
try {
action.setResponseContentType(WebContent.contentTypeJSON);
action.getResponseOutputStream().print(String.valueOf(allJsonPairs));
ServletOps.success(action);
} catch (IOException ex) {
FmtLog.warn(action.log, "[%d] Get all prefixes: Failed to send response: %s", action.id, ex.getMessage());
ServletOps.errorOccurred(ex);
}
}

private static JsonObject jsonObject(String prefix, String uri) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(PrefixUtils.PREFIX, prefix);
jsonObject.addProperty(PrefixUtils.URI, uri);
return jsonObject;
}

private static AcceptList acceptGET = AcceptList.create(WebContent.contentTypeTextPlain, WebContent.contentTypeJSON);
private static MediaType dftMediaType = MediaType.create(WebContent.contentTypeJSON);

private void execFetchURIByPrefix(HttpAction action, PrefixesAccess prefixes, String prefix) {
Optional<String> x = prefixes.fetchURI(prefix);
String namespace = x.orElse(NO_PREFIX_NS);

try {
MediaType mt = ConNeg.chooseContentType(action.getRequest(), acceptGET, dftMediaType);
String ctString = mt.getContentTypeStr();
switch (ctString) {
case WebContent.contentTypeTextPlain -> responseText(action, prefix, namespace);
case WebContent.contentTypeJSON -> responseJSON(action, prefix, namespace);
default ->
ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415);
}
FmtLog.info(action.log, "[%d] %s -> %s", action.id, prefix, namespace);
ServletOps.success(action);
} catch (IOException ex) {
FmtLog.warn(action.log, "[%d] Fetch URI by prefix: Failed to send response: %s", action.id, ex.getMessage());
ServletOps.errorOccurred(ex);
}
FmtLog.info(action.log, "[%d] %s -> %s", action.id, prefix, namespace);
action.commit();
ServletOps.success(action);
}

private static void responseJSON(HttpAction action, String prefix, String uri) throws IOException {
action.setResponseContentType(WebContent.contentTypeJSON);
JsonObject jObj = jsonObject(prefix, uri);
action.getResponseOutputStream().print(String.valueOf(jObj));
}

private static void responseText(HttpAction action, String prefix, String uri) throws IOException {
action.setResponseContentType(WebContent.contentTypeTextPlain);
action.getResponseOutputStream().print(uri);
}

private void execFetchPrefixForURI(HttpAction action, PrefixesAccess prefixes, String uri) {
List<String> prefixList = prefixes.fetchPrefix(uri);
JsonArray prefixJsonArray = new JsonArray();
for (String p : prefixList) {
JsonObject jsonObject2 = jsonObject(p, uri);
prefixJsonArray.add(jsonObject2);
}
FmtLog.info(action.log, "[%d] PrefixForURI: %s: %s", action.id, String.valueOf(prefixJsonArray));

try {
action.setResponseContentType(WebContent.contentTypeJSON);
action.getResponseOutputStream().print(String.valueOf(prefixJsonArray));
} catch (IOException ex) {
FmtLog.warn(action.log, "[%d] Fetch prefixes for URI: Failed to send response: %s", action.id, ex.getMessage());
ServletOps.errorOccurred(ex);
}
action.commit();
ServletOps.success(action);
action.endRead();
}
}
Loading

0 comments on commit 565736c

Please sign in to comment.