rvesse commented on code in PR #2538: URL: https://github.com/apache/jena/pull/2538#discussion_r1642376636
########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionPrefixesR.java: ########## @@ -0,0 +1,156 @@ +/* + * 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.google.gson.JsonArray; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; + +import org.apache.jena.fuseki.servlets.prefixes.ActionPrefixesBase; +import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils; +import org.apache.jena.fuseki.servlets.prefixes.JsonObject; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ActionPrefixesR extends ActionPrefixesBase { + + private static final String NO_PREFIX_NS = ""; + + public ActionPrefixesR() {} + + @Override + protected void doOptions(HttpAction action) { + ActionLib.setCommonHeadersForOptions(action); + action.setResponseHeader(HttpNames.hAllow, "GET,OPTIONS"); + ServletOps.success(action); + } + + public void validateGet(HttpAction action) { + validate(action); + // check if the combination of parameters is legal + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + if(prefix != null && uri != null) { + ServletOps.errorBadRequest("Provide only one of the prefix or uri parameters!"); + return; + } + } + + @Override + protected void doGet(HttpAction action) { + ActionLib.setCommonHeaders(action); + validateGet(action); + + action.beginRead(); + try { + // Not null (valid request) + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + + if (prefix == null && uri == null) { + //getAll + Map<String, String> allPairs = prefixes(action).getAll(); + JsonArray allJsonPairs = new JsonArray(); + allPairs.entrySet().stream() + .forEach(entry -> { + com.google.gson.JsonObject jsonObject = new com.google.gson.JsonObject(); + jsonObject.addProperty("prefix", entry.getKey()); + jsonObject.addProperty("uri", entry.getValue()); + allJsonPairs.add(jsonObject); + FmtLog.info(action.log, "[%d] - %s", action.id, new JsonObject(entry.getKey(), entry.getValue())); + }); + action.setResponseContentType(WebContent.contentTypeJSON); + action.getResponseOutputStream().print(String.valueOf(allJsonPairs)); + + ServletOps.success(action); + action.endRead(); + return; + } + if (prefix != null && uri == null) { Review Comment: Stylistic (and entirely optional): Rather than these if checks which aren't particularly clear why not have a `chooseResponseType()` (or similarly named method) that does those in a short method returning an `enum` value indicating the response type selected and then using a `switch` statement here for clarity? ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionPrefixesRW.java: ########## @@ -0,0 +1,141 @@ +/* + * 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.logging.FmtLog; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; + +import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils; + +import java.io.IOException; + +public class ActionPrefixesRW extends ActionPrefixesR { + + public ActionPrefixesRW() {} + + @Override + protected void doOptions(HttpAction action) { + ActionLib.setCommonHeadersForOptions(action); + action.setResponseHeader(HttpNames.hAllow, "GET,OPTIONS,POST"); + ServletOps.success(action); + } + + public void validateDelete(HttpAction action) { + validate(action); + String prefixToRemove = action.getRequestParameter("removeprefix"); + if (prefixToRemove == null || prefixToRemove.isEmpty() || !PrefixUtils.prefixIsValid(prefixToRemove)) { + ServletOps.errorBadRequest("Remove operation unsuccessful!"); + return; + } + } + + @Override + protected void doDelete(HttpAction action) { + validateDelete(action); + action.beginWrite(); + + try { + String prefixToRemove = action.getRequestParameter("removeprefix"); Review Comment: Since this is a `DELETE` request would it make more sense to just use the same `prefix` parameter as the read endpoints? The use of HTTP verb already differentiates this request from a read request so using a different parameter name seems unnecessary ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionPrefixesR.java: ########## @@ -0,0 +1,156 @@ +/* + * 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.google.gson.JsonArray; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; + +import org.apache.jena.fuseki.servlets.prefixes.ActionPrefixesBase; +import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils; +import org.apache.jena.fuseki.servlets.prefixes.JsonObject; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ActionPrefixesR extends ActionPrefixesBase { + + private static final String NO_PREFIX_NS = ""; + + public ActionPrefixesR() {} + + @Override + protected void doOptions(HttpAction action) { + ActionLib.setCommonHeadersForOptions(action); + action.setResponseHeader(HttpNames.hAllow, "GET,OPTIONS"); + ServletOps.success(action); + } + + public void validateGet(HttpAction action) { + validate(action); + // check if the combination of parameters is legal + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + if(prefix != null && uri != null) { + ServletOps.errorBadRequest("Provide only one of the prefix or uri parameters!"); + return; + } + } + + @Override + protected void doGet(HttpAction action) { + ActionLib.setCommonHeaders(action); + validateGet(action); + + action.beginRead(); + try { + // Not null (valid request) + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + + if (prefix == null && uri == null) { + //getAll + Map<String, String> allPairs = prefixes(action).getAll(); + JsonArray allJsonPairs = new JsonArray(); + allPairs.entrySet().stream() + .forEach(entry -> { + com.google.gson.JsonObject jsonObject = new com.google.gson.JsonObject(); + jsonObject.addProperty("prefix", entry.getKey()); + jsonObject.addProperty("uri", entry.getValue()); + allJsonPairs.add(jsonObject); + FmtLog.info(action.log, "[%d] - %s", action.id, new JsonObject(entry.getKey(), entry.getValue())); + }); + action.setResponseContentType(WebContent.contentTypeJSON); + action.getResponseOutputStream().print(String.valueOf(allJsonPairs)); + + ServletOps.success(action); + action.endRead(); + return; + } + if (prefix != null && uri == null) { + + if (prefix.isEmpty()) { Review Comment: @afs Does it make sense to move some of these checks into `validate()` method or does it not make much difference from a Fuseki request lifecycle perspective? ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/PrefixesService.java: ########## @@ -0,0 +1,57 @@ +/* + * 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.fuseki.server.Operation; +import org.apache.jena.fuseki.server.OperationRegistry; +import org.apache.jena.fuseki.servlets.ActionService; + +/** The PrefixesService class registers the PrefixesR and PrefixesRW operations + * and actions for the Prefixes Service. The service allows for prefix management + * on a given dataset provided in the config.ttl file. When making requests + * to the API the url can have 3 parameters: prefix, uri, and prefixToRemove, + * the combination of which defines the operation performed. The read operations + * are fetching the prefix by URI, fetching the URI by prefix, and fetching all. + * They are evoked via HTTP GET. The read-write operations are removing and updating + * prefix-URI pairs. Those are POST requests. */ + +public class PrefixesService { + public static final Operation operationPrefixesRW; + public static final Operation operationPrefixesR; + + private static final ActionService procPrefixR; + private static final ActionService procPrefixRW; + + static { + operationPrefixesR = Operation.alloc("http://telicent.io/fuseki/operation/prefixes-r", Review Comment: Needs to change to be a Jena controlled namespace ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/prefixes/ActionProcPrefixes.java: ########## @@ -0,0 +1,228 @@ +/* + * 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.prefixes; + +import com.google.gson.JsonArray; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.fuseki.servlets.*; +import org.apache.jena.query.TxnType; +import org.apache.jena.riot.WebContent; +import org.apache.jena.sparql.core.Transactional; + +import java.io.IOException; +import java.util.*; + + +//validate and unpack the HTTP Get request, +//call the storage's fetch URI by the prefix function from the HTTP request + +public class ActionProcPrefixes extends BaseActionREST { + + private final PrefixesAccess storage; + + // The response to a prefix lookup when there is no such prefix. + // This value is not a proper namespace because it is not an absolute URI. + private static final String NO_PREFIX_NS = ""; + + public ActionProcPrefixes(PrefixesAccess storage) { + this.storage = storage; + } + + public void validateGet (HttpAction action) { + + // sanitize parameters + Iterator<String> paramNames = action.getRequestParameterNames().asIterator(); + while(paramNames.hasNext()) { + String check = paramNames.next(); + if(!check.equals("prefix") && !check.equals("uri") && !check.equals("removeprefix")) { + ServletOps.errorBadRequest("Unrecognized parameter"); + return; + } + } + // check if the combination of parameters is legal + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + if(prefix != null && uri != null) { + ServletOps.errorBadRequest("Provide only one of the prefix or uri parameters!"); + return; + } + } + + public void validatePost (HttpAction action) { + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + String prefixToRemove = action.getRequestParameter("removeprefix"); + + if (prefix.isEmpty() && uri.isEmpty() && prefixToRemove.isEmpty()) { + ServletOps.errorBadRequest("Empty operation - unsuccessful!"); + return; + } + if (prefixToRemove.isEmpty()) { + if (prefix.isEmpty() || uri.isEmpty() || !PrefixUtils.prefixIsValid(prefix) || !PrefixUtils.uriIsValid(uri)) { + ServletOps.errorBadRequest("Update operation unsuccessful!"); + return; + } + } + if (prefix.isEmpty() && uri.isEmpty()) { + if (!PrefixUtils.prefixIsValid(prefixToRemove)) { + ServletOps.errorBadRequest("Remove operation unsuccessful!"); + return; + } + } + } + + @Override + public void doGet (HttpAction action) { + + validateGet(action); + + Transactional transactional = storage.transactional(); + transactional.begin(TxnType.READ); + + try { + // Not null (valid request) + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + + if (prefix == null && uri == null) { + //getAll + Map<String, String> allPairs = storage.getAll(); + JsonArray allJsonPairs = new JsonArray(); + allPairs.entrySet().stream() + .forEach(entry -> { + com.google.gson.JsonObject jsonObject = new com.google.gson.JsonObject(); + jsonObject.addProperty("prefix", entry.getKey()); + jsonObject.addProperty("uri", entry.getValue()); + allJsonPairs.add(jsonObject); + FmtLog.info(action.log, "[%d] - %s", action.id, new JsonObject(entry.getKey(), entry.getValue())); + }); + action.setResponseContentType(WebContent.contentTypeJSON); + action.getResponseOutputStream().print(String.valueOf(allJsonPairs)); + + transactional.commit(); + ServletOps.success(action); + transactional.end(); + return; + } + if (prefix != null && uri == null) { + + if (prefix.isEmpty()) { + ServletOps.errorBadRequest("Empty prefix!"); + return; + } + else if (!PrefixUtils.prefixIsValid(prefix)) { + ServletOps.errorBadRequest("Prefix contains illegal characters!"); + return; + } + else { + //fetchURI + Optional<String> x = storage.fetchURI(prefix); + String namespace = x.orElse(NO_PREFIX_NS); + + JsonObject jsonObject = new JsonObject(prefix, namespace); + + // Build the response. + action.setResponseContentType(WebContent.contentTypeJSON); + action.getResponseOutputStream().print(namespace); + // Indicate success + FmtLog.info(action.log, "[%d] %s -> %s", action.id, prefix, jsonObject.toString()); + transactional.commit(); + ServletOps.success(action); + return; + } + } + if (prefix == null && uri != null) { + if (uri.isEmpty()) { + ServletOps.errorBadRequest("Empty URI!"); + return; + } + else if (!PrefixUtils.uriIsValid(uri)) { + ServletOps.errorBadRequest("URI contains illegal characters!"); + return; + } + else { + //fetchPrefix + List<String> prefixList =storage.fetchPrefix(uri); + JsonArray prefixJsonArray = new JsonArray(); + for (String p : prefixList) { + com.google.gson.JsonObject jsonObject2 = new com.google.gson.JsonObject(); + jsonObject2.addProperty("prefix", p); + jsonObject2.addProperty("uri", uri); + prefixJsonArray.add(jsonObject2); + FmtLog.info(action.log, "[%d] - %s", action.id, new JsonObject(p, uri)); + } + // Build the response. + action.setResponseContentType(WebContent.contentTypeJSON); + action.getResponseOutputStream().print(String.valueOf(prefixJsonArray)); + // Indicate success + transactional.commit(); + ServletOps.success(action); + transactional.end(); + return; + } + } + + } catch (RuntimeException | IOException ex) { + try { transactional.abort(); } + catch (Throwable th ) { + FmtLog.warn(action.log, th, "[%d] GET prefix = %s", action.id); + } + ServletOps.errorOccurred(ex); + } finally { + transactional.end(); + } + } + + @Override + public void doPost (HttpAction action) { + validatePost(action); + Transactional transactional = storage.transactional(); + + transactional.begin(TxnType.WRITE); + try { + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + + if(prefix.isEmpty()) { + String prefixToRemove = action.getRequestParameter("removeprefix"); Review Comment: Again same comment about overloading `POST` to be both update and delete while also having a separate `DELETE` operation, would be cleaner to keep `POST` as update only ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/prefixes/PrefixesRDF.java: ########## @@ -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.prefixes; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.jena.query.*; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Transactional; +import org.apache.jena.sparql.exec.QueryExec; +import org.apache.jena.sparql.exec.RowSet; +import org.apache.jena.sparql.exec.UpdateExec; +import org.apache.jena.tdb2.DatabaseMgr; + +/** + * The prefix-URI mappings are represented in blank nodes of type Prefix, with prefixName and prefixURI attributes. + * The prefixName and prefixURI of the same node are a distinct prefix-URI mapping. + * + * PREFIX prefixes: <http://jena.apache.org/prefixes#> + * PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + * PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + * + * [] rdf:type prefixes:Prefix ; + * prefixes:prefixName "test" ; + * prefixes:prefixURI "http://example/testPrefix#"^^xsd:anyURI + * . + * + * [ rdf:type prefixes:Prefix ; + * prefixes:prefixName "ns" ; + * prefixes:prefixURI "http://example/namespace#"^^xsd:anyURI + * ] + */ + +public class PrefixesRDF implements PrefixesAccess { + + DatasetGraph dataset = DatabaseMgr.createDatasetGraph(); + public void setDataset() { + + dataset.begin(ReadWrite.WRITE) ; + try { + + String update1 = """ + PREFIX prefixes: <http://jena.apache.org/prefixes#> + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + + INSERT DATA { + [] rdf:type prefixes:Prefix ; + prefixes:prefixName "prefix1" ; + prefixes:prefixURI "http://www.localhost.org/uri1#"^^xsd:anyURI + }"""; + UpdateExec.dataset(dataset).update(update1).execute(); + + String update2 = """ + PREFIX prefixes: <http://jena.apache.org/prefixes#> + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + + INSERT DATA { + [] rdf:type prefixes:Prefix ; + prefixes:prefixName "prefix2" ; + prefixes:prefixURI "http://www.localhost.org/uri2#"^^xsd:anyURI + }"""; + UpdateExec.dataset(dataset).update(update2).execute(); + + dataset.commit(); + } catch (Throwable th) { + dataset.abort(); + throw(th); + } finally { + dataset.end() ; + } + + } + + public Transactional transactional() { return dataset; } + + @Override + public Optional<String> fetchURI(String prefix) { + + String query = "PREFIX prefixes: <http://jena.apache.org/prefixes#> " + + "SELECT ?prefixURI WHERE { " + + "?X prefixes:prefixName \"" + prefix + "\" . " + + "?X prefixes:prefixURI ?prefixURI " + + "}"; + + try (QueryExec qExec = QueryExec.dataset(dataset).query(query).build()) { + + RowSet rowSet = qExec.select(); + List<String> x = new ArrayList<String>(); + + rowSet.forEachRemaining(row -> { + String prefixNameLiteral = row.get("prefixURI").getLiteralLexicalForm(); + if (prefixNameLiteral != null) { + x.add(prefixNameLiteral); + } + }); + if (x.isEmpty()) + return Optional.empty(); + //originally x.getFirst() + return Optional.ofNullable(x.get(0)); + } + } + + @Override + public void updatePrefix(String prefix, String uri) { + + String query = + "PREFIX prefixes: <http://jena.apache.org/prefixes#>\n" + + "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n" + + "ASK WHERE { ?X prefixes:prefixName \"" + prefix + "\" ; \n" + + " prefixes:prefixURI ?prefixURI . }"; + try (QueryExec qExec = QueryExec.dataset(dataset).query(query).build()) { + + AtomicBoolean result = new AtomicBoolean(false); + dataset.executeRead(()->{ + result.set(qExec.ask()); + }); + + String update; + if (result.get()) + update = + "PREFIX prefixes: <http://jena.apache.org/prefixes#>\n" + Review Comment: Stylistic: Maybe use multi-line strings to make the queries and updates more readable in the code? ########## jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TestPrefixesService.java: ########## @@ -0,0 +1,584 @@ +/* + * 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.main.prefixes; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import org.apache.jena.atlas.web.HttpException; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.servlets.PrefixesService; +import org.apache.jena.sparql.exec.http.Params; +import org.apache.jena.web.HttpSC; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.apache.jena.http.HttpOp.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestPrefixesService { Review Comment: This appears to be a copy paste from `TestAbstractPrefixParam` with the only difference being the Fuseki setup is configuration rather than programmatically driven. Could it not extend that class and simply `@Override` the `before()` and `afterSuite()` methods? ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/prefixes/PrefixesAccess.java: ########## @@ -0,0 +1,42 @@ +/* + * 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.prefixes; + + +import org.apache.jena.sparql.core.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +//interface for interacting with the storages +public interface PrefixesAccess { + + Optional<String> fetchURI(String prefix); + + Transactional transactional(); + + void updatePrefix(String prefix, String uri); + void removePrefix(String prefixToRemove); + + Map<String, String> getAll(); + + /** Fetches the prefixes assigned o the provided URI. There can be multiple in the JsonArray.**/ Review Comment: ```suggestion /** Fetches the prefixes assigned to the provided URI. There can be multiple in the list**/ ``` ########## jena-fuseki2/examples/config-prefixes.ttl: ########## @@ -0,0 +1,24 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX example: <http://example.org/fuseki/> +PREFIX fuseki: <http://jena.apache.org/fuseki#> +PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX : <http://telicent.io/config#> +PREFIX ja: <http://jena.hpl.hp.com/2005/11/Assembler#> +PREFIX tdb2: <http://jena.apache.org/2016/tdb#> + +PREFIX ex: <http://example.org/fuseki/operation/> Review Comment: This namespace does not appear to match that actually used later in the code so unclear this example is actually usable? ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionPrefixesR.java: ########## @@ -0,0 +1,156 @@ +/* + * 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.google.gson.JsonArray; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; + +import org.apache.jena.fuseki.servlets.prefixes.ActionPrefixesBase; +import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils; +import org.apache.jena.fuseki.servlets.prefixes.JsonObject; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ActionPrefixesR extends ActionPrefixesBase { + + private static final String NO_PREFIX_NS = ""; + + public ActionPrefixesR() {} + + @Override + protected void doOptions(HttpAction action) { + ActionLib.setCommonHeadersForOptions(action); + action.setResponseHeader(HttpNames.hAllow, "GET,OPTIONS"); + ServletOps.success(action); + } + + public void validateGet(HttpAction action) { + validate(action); + // check if the combination of parameters is legal + String prefix = action.getRequestParameter("prefix"); Review Comment: Can we have all the parameter and JSON field names defined as constants somewhere please? ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/prefixes/PrefixesRDF.java: ########## @@ -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.prefixes; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.jena.query.*; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Transactional; +import org.apache.jena.sparql.exec.QueryExec; +import org.apache.jena.sparql.exec.RowSet; +import org.apache.jena.sparql.exec.UpdateExec; +import org.apache.jena.tdb2.DatabaseMgr; + +/** + * The prefix-URI mappings are represented in blank nodes of type Prefix, with prefixName and prefixURI attributes. + * The prefixName and prefixURI of the same node are a distinct prefix-URI mapping. + * + * PREFIX prefixes: <http://jena.apache.org/prefixes#> + * PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + * PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + * + * [] rdf:type prefixes:Prefix ; + * prefixes:prefixName "test" ; + * prefixes:prefixURI "http://example/testPrefix#"^^xsd:anyURI + * . + * + * [ rdf:type prefixes:Prefix ; + * prefixes:prefixName "ns" ; + * prefixes:prefixURI "http://example/namespace#"^^xsd:anyURI + * ] + */ + +public class PrefixesRDF implements PrefixesAccess { + + DatasetGraph dataset = DatabaseMgr.createDatasetGraph(); + public void setDataset() { + + dataset.begin(ReadWrite.WRITE) ; + try { + + String update1 = """ + PREFIX prefixes: <http://jena.apache.org/prefixes#> + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + + INSERT DATA { + [] rdf:type prefixes:Prefix ; + prefixes:prefixName "prefix1" ; + prefixes:prefixURI "http://www.localhost.org/uri1#"^^xsd:anyURI + }"""; + UpdateExec.dataset(dataset).update(update1).execute(); + + String update2 = """ + PREFIX prefixes: <http://jena.apache.org/prefixes#> + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + + INSERT DATA { + [] rdf:type prefixes:Prefix ; + prefixes:prefixName "prefix2" ; + prefixes:prefixURI "http://www.localhost.org/uri2#"^^xsd:anyURI + }"""; + UpdateExec.dataset(dataset).update(update2).execute(); + + dataset.commit(); + } catch (Throwable th) { + dataset.abort(); + throw(th); + } finally { + dataset.end() ; + } + + } + + public Transactional transactional() { return dataset; } + + @Override + public Optional<String> fetchURI(String prefix) { + + String query = "PREFIX prefixes: <http://jena.apache.org/prefixes#> " + Review Comment: Could be better to use a parameterised string here i.e. https://jena.apache.org/documentation/query/parameterized-sparql-strings.html ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/prefixes/PrefixesRDF.java: ########## @@ -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.prefixes; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.jena.query.*; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Transactional; +import org.apache.jena.sparql.exec.QueryExec; +import org.apache.jena.sparql.exec.RowSet; +import org.apache.jena.sparql.exec.UpdateExec; +import org.apache.jena.tdb2.DatabaseMgr; + +/** + * The prefix-URI mappings are represented in blank nodes of type Prefix, with prefixName and prefixURI attributes. + * The prefixName and prefixURI of the same node are a distinct prefix-URI mapping. + * + * PREFIX prefixes: <http://jena.apache.org/prefixes#> + * PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + * PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + * + * [] rdf:type prefixes:Prefix ; + * prefixes:prefixName "test" ; + * prefixes:prefixURI "http://example/testPrefix#"^^xsd:anyURI + * . + * + * [ rdf:type prefixes:Prefix ; + * prefixes:prefixName "ns" ; + * prefixes:prefixURI "http://example/namespace#"^^xsd:anyURI + * ] + */ + +public class PrefixesRDF implements PrefixesAccess { + + DatasetGraph dataset = DatabaseMgr.createDatasetGraph(); + public void setDataset() { + + dataset.begin(ReadWrite.WRITE) ; + try { Review Comment: Is this method leftover debugging code? ########## jena-ontapi/src/main/java/org/apache/jena/ontapi/OntModelFactory.java: ########## Review Comment: Unrelated changes from a merge conflict resolution? ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionPrefixesRW.java: ########## @@ -0,0 +1,141 @@ +/* + * 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.logging.FmtLog; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; + +import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils; + +import java.io.IOException; + +public class ActionPrefixesRW extends ActionPrefixesR { + + public ActionPrefixesRW() {} + + @Override + protected void doOptions(HttpAction action) { + ActionLib.setCommonHeadersForOptions(action); + action.setResponseHeader(HttpNames.hAllow, "GET,OPTIONS,POST"); + ServletOps.success(action); + } + + public void validateDelete(HttpAction action) { + validate(action); + String prefixToRemove = action.getRequestParameter("removeprefix"); + if (prefixToRemove == null || prefixToRemove.isEmpty() || !PrefixUtils.prefixIsValid(prefixToRemove)) { + ServletOps.errorBadRequest("Remove operation unsuccessful!"); + return; + } + } + + @Override + protected void doDelete(HttpAction action) { + validateDelete(action); + action.beginWrite(); + + try { + String prefixToRemove = action.getRequestParameter("removeprefix"); + prefixes(action).removePrefix(prefixToRemove); + FmtLog.info(action.log, "[%d] Remove %s:", action.id, prefixToRemove); + + action.commit(); + + // Indicate success + ServletOps.success(action); + + // Build the response. + action.setResponseContentType(WebContent.contentTypeJSON); + action.getResponseOutputStream().print(""); + + } catch (RuntimeException | IOException ex) { + try { action.abort(); } + catch (Throwable th ) { + FmtLog.warn(action.log, th, "[%d] POST prefix = %s", action.id); + } + ServletOps.errorOccurred(ex); + } finally { + action.end(); + } + } + + + public void validatePost(HttpAction action) { + validate(action); + String prefix = action.getRequestParameter("prefix"); + String uri = action.getRequestParameter("uri"); + String prefixToRemove = action.getRequestParameter("removeprefix"); Review Comment: So I guess this is why `removeprefix` is a separate parameter because we have `POST` allowing for both update and delete, might be cleaner to just limit `POST` to updating a prefix and keep `DELETE` for deleting a prefix ########## jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/prefixes/ActionProcPrefixes.java: ########## @@ -0,0 +1,228 @@ +/* + * 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.prefixes; + +import com.google.gson.JsonArray; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.fuseki.servlets.*; +import org.apache.jena.query.TxnType; +import org.apache.jena.riot.WebContent; +import org.apache.jena.sparql.core.Transactional; + +import java.io.IOException; +import java.util.*; + + +//validate and unpack the HTTP Get request, +//call the storage's fetch URI by the prefix function from the HTTP request + +public class ActionProcPrefixes extends BaseActionREST { Review Comment: There seems to be two different implementations of the same thing - this and the `ActionPrefixesR`/`ActionPrefixedRW` combination - can some/all of the logic be shared between these to reduce the duplication? -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
