diff --git a/pom.xml b/pom.xml
index d745a488e..4000207a9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -139,13 +139,13 @@
${project.groupId}
client
- 4.2.10
+ 4.2.11-SNAPSHOT
classes
${project.groupId}
client
- 4.2.10
+ 4.2.11-SNAPSHOT
war
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Add.java b/src/main/java/com/atomgraph/linkeddatahub/resource/Add.java
index 0a12c036d..9bc6b93ad 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/Add.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/Add.java
@@ -19,7 +19,6 @@
import com.atomgraph.core.MediaTypes;
import com.atomgraph.core.vocabulary.SD;
import com.atomgraph.linkeddatahub.client.GraphStoreClient;
-import com.atomgraph.linkeddatahub.model.Service;
import com.atomgraph.linkeddatahub.server.security.AgentContext;
import java.io.InputStream;
import java.io.OutputStream;
@@ -27,18 +26,14 @@
import java.util.Optional;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
-import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.POST;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.StreamingOutput;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.Providers;
-import org.apache.jena.ontology.Ontology;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ResIterator;
import org.apache.jena.rdf.model.Resource;
@@ -67,18 +62,13 @@ public class Add
* @param request current request
* @param uriInfo current URI info
* @param mediaTypes supported media types
- * @param application matched application
- * @param ontology matched application's ontology
- * @param service matched application's service
* @param providers JAX-RS providers
* @param system system application
- * @param securityContext JAX-RS security context
* @param agentContext authenticated agent's context
*/
@Inject
public Add(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
- com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
- @Context SecurityContext securityContext, Optional agentContext,
+ Optional agentContext,
@Context Providers providers, com.atomgraph.linkeddatahub.Application system)
{
this.uriInfo = uriInfo;
@@ -92,12 +82,10 @@ public Add(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaT
* Expects a model containing a resource with dct:source (source URI) and sd:name (target graph URI) properties.
*
* @param model the RDF model containing the import parameters
- * @param defaultGraph whether to import into the default graph
- * @param graphUri the target graph URI
* @return JAX-RS response with the imported data
*/
@POST
- public Response post(Model model, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response post(Model model)
{
ResIterator it = model.listSubjectsWithProperty(DCTerms.source);
try
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java b/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java
index f4a1ccea9..716289439 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java
@@ -16,13 +16,11 @@
*/
package com.atomgraph.linkeddatahub.resource;
-import com.atomgraph.client.util.DataManager;
import com.atomgraph.core.MediaTypes;
+import com.atomgraph.linkeddatahub.apps.model.Application;
import com.atomgraph.linkeddatahub.client.GraphStoreClient;
import com.atomgraph.linkeddatahub.imports.QueryLoader;
-import com.atomgraph.linkeddatahub.model.Service;
-import com.atomgraph.linkeddatahub.server.filter.response.CacheInvalidationFilter;
-import com.atomgraph.linkeddatahub.server.model.impl.GraphStoreImpl;
+import com.atomgraph.linkeddatahub.server.model.impl.DirectGraphStoreImpl;
import com.atomgraph.linkeddatahub.server.security.AgentContext;
import com.atomgraph.linkeddatahub.server.util.Skolemizer;
import com.atomgraph.linkeddatahub.vocabulary.LDH;
@@ -37,18 +35,15 @@
import java.util.UUID;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
-import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.POST;
-import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.container.ResourceContext;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
+import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
-import jakarta.ws.rs.ext.Providers;
-import org.apache.jena.ontology.Ontology;
import org.apache.jena.query.ParameterizedSparqlString;
import org.apache.jena.query.Query;
import org.apache.jena.query.Syntax;
@@ -56,10 +51,8 @@
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.ResIterator;
import org.apache.jena.rdf.model.Resource;
-import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.vocabulary.DCTerms;
import org.apache.jena.vocabulary.RDF;
-import org.glassfish.jersey.uri.UriComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,39 +61,53 @@
*
* @author {@literal Martynas Jusevičius }
*/
-public class Generate extends GraphStoreImpl
+public class Generate
{
private static final Logger log = LoggerFactory.getLogger(Generate.class);
+
+ private final UriInfo uriInfo;
+ private final MediaTypes mediaTypes;
+ private final Application application;
+ private final Optional agentContext;
+ private final com.atomgraph.linkeddatahub.Application system;
+ private final ResourceContext resourceContext;
/**
* Constructs endpoint for container generation.
- *
+ *
* @param request current request
* @param uriInfo current URI info
* @param mediaTypes supported media types
* @param application matched application
- * @param ontology matched application's ontology
- * @param service matched application's service
- * @param providers JAX-RS providers
* @param system system application
- * @param securityContext JAX-RS security context
* @param agentContext authenticated agent's context
- * @param dataManager RDF data manager
+ * @param resourceContext resource context for creating resources
*/
@Inject
public Generate(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
- com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
- @Context SecurityContext securityContext, Optional agentContext,
- @Context Providers providers, com.atomgraph.linkeddatahub.Application system,
- DataManager dataManager)
+ com.atomgraph.linkeddatahub.apps.model.Application application, Optional agentContext,
+ com.atomgraph.linkeddatahub.Application system, @Context ResourceContext resourceContext)
{
- super(request, uriInfo, mediaTypes, application, ontology, service, securityContext, agentContext, providers, system);
+ this.uriInfo = uriInfo;
+ this.mediaTypes = mediaTypes;
+ this.application = application;
+ this.agentContext = agentContext;
+ this.system = system;
+ this.resourceContext = resourceContext;
}
-
+
+ /**
+ * Generates containers for given classes.
+ * Expects a model containing a parent container (sioc:has_parent) and one or more class specifications
+ * with void:class and spin:query properties. Creates a new container for each class with a view based
+ * on the provided SPARQL SELECT query.
+ *
+ * @param model the RDF model containing the generation parameters
+ * @return JAX-RS response indicating success or failure
+ */
@POST
- @Override
- public Response post(Model model, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response post(Model model)
{
ResIterator it = model.listSubjectsWithProperty(SIOC.HAS_PARENT);
try
@@ -143,9 +150,10 @@ public Response post(Model model, @QueryParam("default") @DefaultValue("false")
service)));
new Skolemizer(containerGraphURI.toString()).apply(containerModel);
- try (Response containerResponse = super.post(containerModel, false, containerGraphURI))
+ // append triples directly to the graph store without doing an HTTP request (and thus no ACL check)
+ try (Response containerResponse = getResourceContext().getResource(DirectGraphStoreImpl.class).post(containerModel, false, containerGraphURI))
{
- if (containerResponse.getStatus() != Response.Status.CREATED.getStatusCode())
+ if (!containerResponse.getStatusInfo().getFamily().equals(Status.Family.SUCCESSFUL))
{
if (log.isErrorEnabled()) log.error("Cannot create container");
throw new InternalServerErrorException("Cannot create container");
@@ -159,10 +167,7 @@ public Response post(Model model, @QueryParam("default") @DefaultValue("false")
}
// ban the parent container URI from proxy cache to make sure the next query using it will be fresh (e.g. SELECT that loads children)
- try (Response response = ban(getApplication().getService().getBackendProxy(), parent.getURI()))
- {
- // Response automatically closed by try-with-resources
- }
+ getSystem().ban(getApplication().getService().getBackendProxy(), parent.getURI(), true);
return Response.ok().build();
}
@@ -211,7 +216,7 @@ public Resource createContainer(Model model, URI graphURI, Resource parent, Stri
addLiteral(DCTerms.title, title).
addLiteral(DH.slug, UUID.randomUUID().toString()).
addLiteral(DCTerms.created, Calendar.getInstance()).
- addProperty(ResourceFactory.createProperty(RDF.getURI(), "_1"), content);
+ addProperty(model.createProperty(RDF.getURI(), "_1"), content); // TO-DO: make sure we're creating sequence value larger than the existing ones?
}
/**
@@ -228,20 +233,64 @@ public Resource createView(Model model, Resource query)
addProperty(SPIN.query, query);
}
- /**
- * Bans URL from the backend proxy cache.
- *
- * @param proxy proxy server URL
- * @param url banned URL
- * @return proxy server response
+ /**
+ * Returns the supported media types.
+ *
+ * @return media types
*/
- public Response ban(Resource proxy, String url)
+ public MediaTypes getMediaTypes()
{
- if (url == null) throw new IllegalArgumentException("Resource cannot be null");
-
- return getSystem().getClient().target(proxy.getURI()).request().
- header(CacheInvalidationFilter.HEADER_NAME, UriComponent.encode(url, UriComponent.Type.UNRESERVED)). // the value has to be URL-encoded in order to match request URLs in Varnish
- method("BAN", Response.class);
+ return mediaTypes;
}
-
+
+ /**
+ * Returns the current application.
+ *
+ * @return the application
+ */
+ public Application getApplication()
+ {
+ return application;
+ }
+
+ /**
+ * Returns the current URI info.
+ *
+ * @return URI info
+ */
+ public UriInfo getUriInfo()
+ {
+ return uriInfo;
+ }
+
+ /**
+ * Returns the authenticated agent's context.
+ *
+ * @return optional agent context
+ */
+ public Optional getAgentContext()
+ {
+ return agentContext;
+ }
+
+ /**
+ * Returns the system application.
+ *
+ * @return system application
+ */
+ public com.atomgraph.linkeddatahub.Application getSystem()
+ {
+ return system;
+ }
+
+ /**
+ * Returns the resource context.
+ *
+ * @return resource context
+ */
+ public ResourceContext getResourceContext()
+ {
+ return resourceContext;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Transform.java b/src/main/java/com/atomgraph/linkeddatahub/resource/Transform.java
index cc449de4f..19366b18c 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/Transform.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/Transform.java
@@ -16,14 +16,12 @@
*/
package com.atomgraph.linkeddatahub.resource;
-import com.atomgraph.client.util.DataManager;
import com.atomgraph.core.MediaTypes;
import com.atomgraph.core.vocabulary.SD;
import com.atomgraph.linkeddatahub.client.GraphStoreClient;
import com.atomgraph.linkeddatahub.imports.QueryLoader;
-import com.atomgraph.linkeddatahub.model.Service;
import com.atomgraph.linkeddatahub.server.io.ValidatingModelProvider;
-import com.atomgraph.linkeddatahub.server.model.impl.GraphStoreImpl;
+import com.atomgraph.linkeddatahub.server.model.impl.DirectGraphStoreImpl;
import com.atomgraph.linkeddatahub.server.security.AgentContext;
import com.atomgraph.linkeddatahub.vocabulary.NFO;
import com.atomgraph.spinrdf.vocabulary.SPIN;
@@ -36,20 +34,19 @@
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotAllowedException;
import jakarta.ws.rs.POST;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.container.ResourceContext;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Providers;
import org.apache.jena.atlas.RuntimeIOException;
-import org.apache.jena.ontology.Ontology;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.Syntax;
@@ -67,39 +64,69 @@
*
* @author {@literal Martynas Jusevičius }
*/
-public class Transform extends GraphStoreImpl
+public class Transform
{
private static final Logger log = LoggerFactory.getLogger(Transform.class);
+ private final UriInfo uriInfo;
+ private final MediaTypes mediaTypes;
+ private final com.atomgraph.linkeddatahub.apps.model.Application application;
+ private final Optional agentContext;
+ private final Providers providers;
+ private final com.atomgraph.linkeddatahub.Application system;
+ private final ResourceContext resourceContext;
+
/**
* Constructs endpoint for synchronous RDF data imports.
*
* @param request current request
* @param uriInfo current URI info
* @param mediaTypes supported media types
- * @param application matched application
- * @param ontology matched application's ontology
- * @param service matched application's service
+ * @param application current application
* @param providers JAX-RS providers
* @param system system application
- * @param securityContext JAX-RS security context
* @param agentContext authenticated agent's context
- * @param dataManager RDF data manager
+ * @param resourceContext resource context
*/
@Inject
public Transform(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
- com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
- @Context SecurityContext securityContext, Optional agentContext,
+ com.atomgraph.linkeddatahub.apps.model.Application application,
+ Optional agentContext,
@Context Providers providers, com.atomgraph.linkeddatahub.Application system,
- DataManager dataManager)
+ @Context ResourceContext resourceContext)
+ {
+ this.uriInfo = uriInfo;
+ this.mediaTypes = mediaTypes;
+ this.application = application;
+ this.agentContext = agentContext;
+ this.providers = providers;
+ this.system = system;
+ this.resourceContext = resourceContext;
+ }
+
+ /**
+ * Rejects GET requests on this endpoint.
+ *
+ * @return never returns normally
+ * @throws NotAllowedException always thrown to indicate GET is not supported
+ */
+ @GET
+ public Response get()
{
- super(request, uriInfo, mediaTypes, application, ontology, service, securityContext, agentContext, providers, system);
+ throw new NotAllowedException("GET is not allowed on this endpoint");
}
+ /**
+ * Transforms RDF data from a remote source using a SPARQL CONSTRUCT query and adds it to a target graph.
+ * Validates URIs to prevent SSRF attacks before processing.
+ *
+ * @param model RDF model containing transformation parameters (dct:source, sd:name, spin:query)
+ * @return HTTP response from forwarding the transformed data to the target graph
+ * @throws BadRequestException if required parameters are missing or invalid
+ */
@POST
- @Override
- public Response post(Model model, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response post(Model model)
{
ResIterator it = model.listSubjectsWithProperty(DCTerms.source);
try
@@ -145,24 +172,24 @@ public Response post(Model model, @QueryParam("default") @DefaultValue("false")
* Handles multipart requests with RDF files.
*
* @param multiPart multipart request object
- * @param defaultGraph true if default graph was specified
- * @param graphUri graph name
* @return response
*/
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
- public Response postMultipart(FormDataMultiPart multiPart, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response postMultipart(FormDataMultiPart multiPart)
{
if (log.isDebugEnabled()) log.debug("MultiPart fields: {} body parts: {}", multiPart.getFields(), multiPart.getBodyParts());
try
{
- Model model = parseModel(multiPart); // do not skolemize because we don't know the graphUri yet
+ DirectGraphStoreImpl graphStore = getResourceContext().getResource(DirectGraphStoreImpl.class);
+
+ Model model = graphStore.parseModel(multiPart); // do not skolemize because we don't know the graphUri yet
MessageBodyReader reader = getProviders().getMessageBodyReader(Model.class, null, null, com.atomgraph.core.MediaType.APPLICATION_NTRIPLES_TYPE);
if (reader instanceof ValidatingModelProvider validatingModelProvider) model = validatingModelProvider.processRead(model);
if (log.isDebugEnabled()) log.debug("POSTed Model size: {}", model.size());
- return postFileBodyPart(model, getFileNameBodyPartMap(multiPart)); // do not write the uploaded file -- instead append its triples/quads
+ return postFileBodyPart(model, graphStore.getFileNameBodyPartMap(multiPart)); // do not write the uploaded file -- instead append its triples/quads
}
catch (URISyntaxException ex)
{
@@ -287,4 +314,74 @@ protected static void validateNotInternalURL(URI uri)
}
}
+ /**
+ * Returns the supported media types.
+ *
+ * @return media types
+ */
+ public MediaTypes getMediaTypes()
+ {
+ return mediaTypes;
+ }
+
+ /**
+ * Returns the current application.
+ *
+ * @return application resource
+ */
+ public com.atomgraph.linkeddatahub.apps.model.Application getApplication()
+ {
+ return application;
+ }
+
+ /**
+ * Returns the current URI info.
+ *
+ * @return URI info
+ */
+ public UriInfo getUriInfo()
+ {
+ return uriInfo;
+ }
+
+ /**
+ * Returns the authenticated agent's context.
+ *
+ * @return optional agent context
+ */
+ public Optional getAgentContext()
+ {
+ return agentContext;
+ }
+
+ /**
+ * Returns the registry of JAX-RS providers.
+ *
+ * @return JAX-RS providers registry
+ */
+ public Providers getProviders()
+ {
+ return providers;
+ }
+
+ /**
+ * Returns the system application.
+ *
+ * @return system application
+ */
+ public com.atomgraph.linkeddatahub.Application getSystem()
+ {
+ return system;
+ }
+
+ /**
+ * Returns the JAX-RS resource context.
+ *
+ * @return resource context
+ */
+ public ResourceContext getResourceContext()
+ {
+ return resourceContext;
+ }
+
}
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/acl/Access.java b/src/main/java/com/atomgraph/linkeddatahub/resource/acl/Access.java
index 021523308..f22b7378e 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/acl/Access.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/acl/Access.java
@@ -18,9 +18,6 @@
import com.atomgraph.client.util.HTMLMediaTypePredicate;
import com.atomgraph.core.MediaTypes;
-import static com.atomgraph.core.model.SPARQLEndpoint.DEFAULT_GRAPH_URI;
-import static com.atomgraph.core.model.SPARQLEndpoint.NAMED_GRAPH_URI;
-import static com.atomgraph.core.model.SPARQLEndpoint.QUERY;
import com.atomgraph.core.util.ModelUtils;
import com.atomgraph.linkeddatahub.apps.model.AdminApplication;
import com.atomgraph.linkeddatahub.apps.model.Application;
@@ -36,7 +33,6 @@
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.GET;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.Request;
@@ -47,7 +43,6 @@
import java.util.List;
import java.util.Optional;
import org.apache.jena.query.ParameterizedSparqlString;
-import org.apache.jena.query.Query;
import org.apache.jena.query.QuerySolutionMap;
import org.apache.jena.query.ResultSetRewindable;
import org.apache.jena.rdf.model.Model;
@@ -106,14 +101,10 @@ public Access(@Context Request request, @Context UriInfo uriInfo, MediaTypes med
/**
* Implements the HTTP GET method for retrieving access control information.
*
- * @param unused SPARQL query parameter (unused)
- * @param defaultGraphUris default graph URIs
- * @param namedGraphUris named graph URIs
* @return response with access control data
*/
@GET
- public Response get(@QueryParam(QUERY) Query unused,
- @QueryParam(DEFAULT_GRAPH_URI) List defaultGraphUris, @QueryParam(NAMED_GRAPH_URI) List namedGraphUris)
+ public Response get()
{
final Agent agent = getAgentContext().map(AgentContext::getAgent).orElse(null);
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/acl/AccessRequest.java b/src/main/java/com/atomgraph/linkeddatahub/resource/acl/AccessRequest.java
index 8e2a60699..859efa01f 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/acl/AccessRequest.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/acl/AccessRequest.java
@@ -30,12 +30,10 @@
import com.atomgraph.linkeddatahub.vocabulary.SIOC;
import jakarta.inject.Inject;
import jakarta.servlet.ServletConfig;
-import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.NotAllowedException;
import jakarta.ws.rs.POST;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
@@ -100,12 +98,10 @@ public AccessRequest(com.atomgraph.linkeddatahub.apps.model.Application applicat
/**
* Implements the HTTP GET method.
*
- * @param defaultGraph default graph flag
- * @param graphUri graph URI
* @return response object
*/
@GET
- public Response get(@QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response get()
{
throw new NotAllowedException("GET is not allowed on this endpoint");
}
@@ -114,12 +110,10 @@ public Response get(@QueryParam("default") @DefaultValue("false") Boolean defaul
* Implements the HTTP POST method for submitting access requests.
*
* @param model RDF model with access request data
- * @param defaultGraph default graph flag
- * @param graphUri graph URI
* @return response object
*/
@POST
- public Response post(Model model, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response post(Model model)
{
ResIterator it = model.listResourcesWithProperty(RDF.type, ACL.Authorization);
try
@@ -128,7 +122,7 @@ public Response post(Model model, @QueryParam("default") @DefaultValue("false")
{
Resource authorization = it.next();
- graphUri = getAuthRequestContainerUriBuilder().path(UUID.randomUUID().toString() + "/").build(); // URI of the new access request graph
+ URI graphUri = getAuthRequestContainerUriBuilder().path(UUID.randomUUID().toString() + "/").build(); // URI of the new access request graph
Model requestModel = ModelFactory.createDefaultModel();
Resource agent = authorization.getPropertyResourceValue(ACL.agent);
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java
index c8f65f42a..bb161a509 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java
@@ -26,7 +26,7 @@
import com.atomgraph.linkeddatahub.model.Service;
import com.atomgraph.linkeddatahub.listener.EMailListener;
import com.atomgraph.linkeddatahub.server.filter.response.CacheInvalidationFilter;
-import com.atomgraph.linkeddatahub.server.model.impl.GraphStoreImpl;
+import com.atomgraph.linkeddatahub.server.model.impl.DirectGraphStoreImpl;
import com.atomgraph.linkeddatahub.server.security.AgentContext;
import com.atomgraph.linkeddatahub.server.util.MessageBuilder;
import com.atomgraph.linkeddatahub.server.util.Skolemizer;
@@ -64,10 +64,7 @@
import jakarta.inject.Inject;
import jakarta.mail.MessagingException;
import jakarta.servlet.ServletConfig;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@@ -101,7 +98,7 @@
*
* @author Martynas Jusevičius {@literal }
*/
-public class SignUp extends GraphStoreImpl
+public class SignUp extends DirectGraphStoreImpl
{
private static final Logger log = LoggerFactory.getLogger(SignUp.class);
@@ -180,16 +177,9 @@ public SignUp(@Context Request request, @Context UriInfo uriInfo, MediaTypes med
download = uriInfo.getQueryParameters().containsKey("download"); // debug param that allows downloading the certificate
}
- @GET
- @Override
- public Response get(@QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
- {
- return super.get(false, getURI());
- }
-
@POST
@Override
- public Response post(Model agentModel, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response post(Model agentModel)
{
URI agentGraphUri = getUriInfo().getBaseUriBuilder().path(AGENT_PATH).path("{slug}/").build(UUID.randomUUID().toString());
new Skolemizer(agentGraphUri.toString()).apply(agentModel);
@@ -519,16 +509,6 @@ public Service getAgentService()
{
return getApplication().getService();
}
-
- /**
- * Returns URI of this resource.
- *
- * @return resource URI
- */
- public URI getURI()
- {
- return getUriInfo().getAbsolutePath();
- }
/**
* Returns the number of days until the WebID certificate expires.
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/upload/Item.java b/src/main/java/com/atomgraph/linkeddatahub/resource/upload/Item.java
index 41876ac2d..90fae8a00 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/upload/Item.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/resource/upload/Item.java
@@ -29,29 +29,24 @@
import com.atomgraph.core.MediaTypes;
import com.atomgraph.linkeddatahub.model.Service;
import com.atomgraph.linkeddatahub.server.io.FileRangeOutput;
-import com.atomgraph.linkeddatahub.server.security.AgentContext;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
import java.util.Optional;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
-import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotAcceptableException;
import jakarta.ws.rs.NotFoundException;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response.Status;
-import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
-import org.apache.jena.ontology.Ontology;
+import jakarta.ws.rs.core.Variant.VariantListBuilder;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.sparql.vocabulary.FOAF;
import org.apache.jena.vocabulary.DCTerms;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,8 +56,9 @@
*
* @author Martynas Jusevičius {@literal }
*/
-public class Item extends com.atomgraph.linkeddatahub.resource.Graph
+public class Item
{
+
private static final Logger log = LoggerFactory.getLogger(Item.class);
private static final String ACCEPT_RANGES = "Accept-Ranges";
@@ -71,7 +67,11 @@ public class Item extends com.atomgraph.linkeddatahub.resource.Graph
private static final String CONTENT_RANGE = "Content-Range";
private static final int CHUNK_SIZE = 1024 * 1024; // 1MB chunks
+ private final Request request;
+ private final UriInfo uriInfo;
+ private final Service service;
private final Resource resource;
+ private final com.atomgraph.linkeddatahub.Application system;
private final HttpHeaders httpHeaders;
/**
@@ -80,26 +80,24 @@ public class Item extends com.atomgraph.linkeddatahub.resource.Graph
* @param request current request
* @param uriInfo URI information of the current request
* @param mediaTypes a registry of readable/writable media types
- * @param application current application
- * @param ontology ontology of the current application
* @param service SPARQL service of the current application
- * @param securityContext JAX-RS security context
- * @param agentContext authenticated agent's context
* @param providers JAX-RS provider registry
* @param system system application
* @param httpHeaders request headers
*/
@Inject
public Item(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
- com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
- @Context SecurityContext securityContext, Optional agentContext,
+ Optional service,
@Context Providers providers, com.atomgraph.linkeddatahub.Application system,
@Context HttpHeaders httpHeaders)
{
- super(request, uriInfo, mediaTypes, application, ontology, service, securityContext, agentContext, providers, system);
+ this.request = request;
+ this.uriInfo = uriInfo;
+ this.service = service.get();
this.resource = ModelFactory.createDefaultModel().createResource(uriInfo.getAbsolutePath().toString());
- if (log.isDebugEnabled()) log.debug("Constructing {}", getClass());
+ this.system = system;
this.httpHeaders = httpHeaders;
+ if (log.isDebugEnabled()) log.debug("Constructing {}", getClass());
}
/**
@@ -110,38 +108,52 @@ public void init()
{
getResource().getModel().add(describe());
}
-
+
+ /**
+ * Handles GET requests for uploaded files.
+ * Evaluates HTTP preconditions and serves file content with appropriate Content-Security-Policy headers.
+ *
+ * @return HTTP response with file content or 304 Not Modified
+ */
@GET
- @Override
- public Response get(@QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUri)
+ public Response get()
{
- return getResponseBuilder(getResource().getModel(), graphUri).build();
+ return getResponseBuilder(getResource().getModel(), getURI()).build();
}
-
- @Override
+
+ /**
+ * Builds HTTP response for file requests.
+ * Handles content negotiation, HTTP precondition evaluation (ETag-based caching),
+ * byte-range requests, and applies Content-Security-Policy headers.
+ *
+ * @param model RDF model describing the file
+ * @param graphUri the graph URI (not used for binary file responses)
+ * @return response builder configured for file serving
+ */
public ResponseBuilder getResponseBuilder(Model model, URI graphUri)
{
// do not pass language list as languages do not apply to binary files
- List variants = com.atomgraph.core.model.impl.Response.getVariants(getWritableMediaTypes(Model.class), Collections.emptyList(), getEncodings());
+ List variants = VariantListBuilder.newInstance().mediaTypes(getMediaType()).build();
Variant variant = getRequest().selectVariant(variants);
- if (variant == null)
+ if (variant == null || !getMediaType().isCompatible(variant.getMediaType()))
{
if (log.isTraceEnabled()) log.trace("Requested Variant {} is not on the list of acceptable Response Variants: {}", variant, variants);
throw new NotAcceptableException();
}
- // respond with file content if Variant is compatible with the File's MediaType. otherwise, send RDF
- if (getMediaType().isCompatible(variant.getMediaType()))
- {
- URI fileURI = getSystem().getUploadRoot().resolve(getUriInfo().getPath());
- File file = new File(fileURI);
+ EntityTag entityTag = getEntityTag();
+ ResponseBuilder rb = getRequest().evaluatePreconditions(entityTag);
+ if (rb != null) return rb; // file not modified
+
+ URI fileURI = getSystem().getUploadRoot().resolve(getUriInfo().getPath());
+ File file = new File(fileURI);
- if (!file.exists()) throw new NotFoundException(new FileNotFoundException("File '" + getUriInfo().getPath() + "' not found"));
+ if (!file.exists()) throw new NotFoundException(new FileNotFoundException("File '" + getUriInfo().getPath() + "' not found"));
+
+ if (getHttpHeaders().getRequestHeaders().containsKey(RANGE))
+ {
+ String range = getHttpHeaders().getHeaderString(RANGE);
- if (getHttpHeaders().getRequestHeaders().containsKey(RANGE))
- {
- String range = getHttpHeaders().getHeaderString(RANGE);
-
// if (getHttpHeaders().getRequestHeaders().containsKey(IF_RANGE)) {
// String ifRangeHeader = getHttpHeaders().getHeaderString(IF_RANGE);
//
@@ -157,34 +169,32 @@ public ResponseBuilder getResponseBuilder(Model model, URI graphUri)
//// }
// }
// else
- {
- FileRangeOutput rangeOutput = getFileRangeOutput(file, range);
- final long to = rangeOutput.getLength() + rangeOutput.getFrom();
- String contentRangeValue = String.format("bytes %d-%d/%d", rangeOutput.getFrom(), to - 1, rangeOutput.getFile().length());
-
- return super.getResponseBuilder(model, graphUri).
- status(Status.PARTIAL_CONTENT).
- entity(rangeOutput).
- type(variant.getMediaType()).
- lastModified(getLastModified(file)).
- header(HttpHeaders.CONTENT_LENGTH, rangeOutput.getLength()). // should override Transfer-Encoding: chunked
- header(ACCEPT_RANGES, BYTES_RANGE).
- header(CONTENT_RANGE, contentRangeValue).
- header("Content-Security-Policy", "default-src 'none'; sandbox"); // LNK-011 fix: prevent XSS in uploaded HTML files
- }
- }
+ {
+ FileRangeOutput rangeOutput = getFileRangeOutput(file, range);
+ final long to = rangeOutput.getLength() + rangeOutput.getFrom();
+ String contentRangeValue = String.format("bytes %d-%d/%d", rangeOutput.getFrom(), to - 1, rangeOutput.getFile().length());
- return super.getResponseBuilder(model, graphUri).
- entity(file).
- type(variant.getMediaType()).
- lastModified(getLastModified(file)).
- header(HttpHeaders.CONTENT_LENGTH, file.length()). // should override Transfer-Encoding: chunked
- header(ACCEPT_RANGES, BYTES_RANGE).
- header("Content-Security-Policy", "default-src 'none'; sandbox"); // LNK-011 fix: prevent XSS in uploaded HTML files
- //header("Content-Disposition", "attachment; filename=\"" + getRequiredProperty(NFO.fileName).getString() + "\"").
+ return Response.status(Status.PARTIAL_CONTENT).
+ entity(rangeOutput).
+ type(variant.getMediaType()).
+ tag(entityTag).
+ lastModified(getLastModified(file)).
+ header(HttpHeaders.CONTENT_LENGTH, rangeOutput.getLength()). // should override Transfer-Encoding: chunked
+ header(ACCEPT_RANGES, BYTES_RANGE).
+ header(CONTENT_RANGE, contentRangeValue).
+ header("Content-Security-Policy", "default-src 'none'; sandbox"); // LNK-011 fix: prevent XSS in uploaded HTML files
+ }
}
-
- return super.getResponseBuilder(model, graphUri);
+
+ return Response.ok().
+ entity(file).
+ type(variant.getMediaType()).
+ tag(entityTag).
+ lastModified(getLastModified(file)).
+ header(HttpHeaders.CONTENT_LENGTH, file.length()). // should override Transfer-Encoding: chunked
+ header(ACCEPT_RANGES, BYTES_RANGE).
+ header("Content-Security-Policy", "default-src 'none'; sandbox"); // LNK-011 fix: prevent XSS in uploaded HTML files
+ //header("Content-Disposition", "attachment; filename=\"" + getRequiredProperty(NFO.fileName).getString() + "\"").
}
/**
@@ -237,11 +247,15 @@ public FileRangeOutput getFileRangeOutput(File file, String range)
final long length = to - from;
return new FileRangeOutput(file, from, length);
}
-
- @Override
- public EntityTag getEntityTag(Model model)
+
+ /**
+ * Returns the ETag for HTTP caching based on the file's SHA1 hash.
+ *
+ * @return entity tag for cache validation
+ */
+ public EntityTag getEntityTag()
{
- return null; // disable ETag based on Model hash
+ return new EntityTag(getSHA1Hash(getResource()));
}
/**
@@ -271,14 +285,16 @@ public jakarta.ws.rs.core.MediaType getMediaType()
return com.atomgraph.linkeddatahub.MediaType.valueOf(format);
}
-
- @Override
+
+ /**
+ * Returns the list of media types that can be used to write this file's content.
+ *
+ * @param clazz the class type (not used, file has single media type)
+ * @return list containing the file's media type
+ */
public List getWritableMediaTypes(Class clazz)
{
- List list = new ArrayList<>();
- list.add(getMediaType());
-
- return list;
+ return List.of(getMediaType());
}
/**
@@ -293,14 +309,55 @@ public Model describe()
}
/**
- * Returns URI of this file.
+ * Returns SHA1 property value of the specified resource.
*
- * @return file URI
+ * @param resource RDF resource
+ * @return SHA1 hash string
+ */
+ public String getSHA1Hash(Resource resource)
+ {
+ return resource.getRequiredProperty(FOAF.sha1).getString();
+ }
+
+ /**
+ * Returns the absolute URI of this file resource.
+ *
+ * @return the file's URI
*/
public URI getURI()
{
return getUriInfo().getAbsolutePath();
}
+
+ /**
+ * Returns the current JAX-RS request.
+ *
+ * @return request object
+ */
+ public Request getRequest()
+ {
+ return request;
+ }
+
+ /**
+ * Returns the URI information of the current request.
+ *
+ * @return URI info
+ */
+ public UriInfo getUriInfo()
+ {
+ return uriInfo;
+ }
+
+ /**
+ * Returns the SPARQL service of the current application.
+ *
+ * @return SPARQL service
+ */
+ public Service getService()
+ {
+ return service;
+ }
/**
* Returns RDF resource of this file.
@@ -311,7 +368,17 @@ public Resource getResource()
{
return resource;
}
-
+
+ /**
+ * Returns the system application instance.
+ *
+ * @return system application
+ */
+ public com.atomgraph.linkeddatahub.Application getSystem()
+ {
+ return system;
+ }
+
/**
* Returns HTTP headers of the current request.
*
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/upload/sha1/Item.java b/src/main/java/com/atomgraph/linkeddatahub/resource/upload/sha1/Item.java
deleted file mode 100644
index a0120e520..000000000
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/upload/sha1/Item.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * Copyright 2019 Martynas Jusevičius
- *
- * Licensed 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 com.atomgraph.linkeddatahub.resource.upload.sha1;
-
-import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.Request;
-import jakarta.ws.rs.ext.Providers;
-import com.atomgraph.core.MediaTypes;
-import com.atomgraph.linkeddatahub.model.Service;
-import com.atomgraph.linkeddatahub.server.security.AgentContext;
-import java.io.File;
-import java.util.Date;
-import java.util.Optional;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.core.EntityTag;
-import jakarta.ws.rs.core.HttpHeaders;
-import jakarta.ws.rs.core.SecurityContext;
-import jakarta.ws.rs.core.UriInfo;
-import org.apache.jena.ontology.Ontology;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.Resource;
-import org.apache.jena.sparql.vocabulary.FOAF;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * JAX-RS resource that serves content-addressed (using SHA1 hash) file data.
- *
- * @author Martynas Jusevičius {@literal }
- */
-public class Item extends com.atomgraph.linkeddatahub.resource.upload.Item
-{
- private static final Logger log = LoggerFactory.getLogger(Item.class);
-
- /**
- * Constructs resource.
- *
- * @param request current request
- * @param uriInfo URI information of the current request
- * @param mediaTypes a registry of readable/writable media types
- * @param application current application
- * @param ontology ontology of the current application
- * @param service SPARQL service of the current application
- * @param securityContext JAX-RS security context
- * @param agentContext authenticated agent's context
- * @param providers JAX-RS provider registry
- * @param system system application
- * @param httpHeaders request headers
- */
- @Inject
- public Item(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
- com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
- @Context SecurityContext securityContext, Optional agentContext,
- @Context Providers providers, com.atomgraph.linkeddatahub.Application system,
- @Context HttpHeaders httpHeaders)
- {
- super(request, uriInfo, mediaTypes, application, ontology, service, securityContext, agentContext, providers, system, httpHeaders);
- if (log.isDebugEnabled()) log.debug("Constructing {}", getClass());
- }
-
- @Override
- protected Date getLastModified(File file)
- {
- return null; // disable Last-Modified because we're using ETag here
- }
-
- @Override
- public EntityTag getEntityTag(Model model)
- {
- return new EntityTag(getSHA1Hash(getResource()));
- }
-
- /**
- * Returns SHA1 property value of the specified resource.
- *
- * @param resource RDF resource
- * @return SHA1 hash string
- */
- public String getSHA1Hash(Resource resource)
- {
- return resource.getRequiredProperty(FOAF.sha1).getString();
- }
-
-}
diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/model/Patchable.java b/src/main/java/com/atomgraph/linkeddatahub/server/model/Patchable.java
index 773074251..9c60b8c96 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/server/model/Patchable.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/server/model/Patchable.java
@@ -16,9 +16,7 @@
*/
package com.atomgraph.linkeddatahub.server.model;
-import java.net.URI;
import jakarta.ws.rs.PATCH;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Response;
import org.apache.jena.update.UpdateRequest;
@@ -34,10 +32,9 @@ public interface Patchable
* Handles PATCH request.SPARQL update is used as the patch format.
*
* @param updateRequest SPARQL update
- * @param graphUri named graph URI
* @return response
* @see HTTP PATCH
*/
- @PATCH Response patch(UpdateRequest updateRequest, @QueryParam("graph") URI graphUri);
+ @PATCH Response patch(UpdateRequest updateRequest);
}
diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Graph.java b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DirectGraphStoreImpl.java
similarity index 77%
rename from src/main/java/com/atomgraph/linkeddatahub/resource/Graph.java
rename to src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DirectGraphStoreImpl.java
index d170d7f56..e5a73fcd7 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/resource/Graph.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DirectGraphStoreImpl.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2021 Martynas Jusevičius
+ * Copyright 2019 Martynas Jusevičius
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,13 @@
* limitations under the License.
*
*/
-package com.atomgraph.linkeddatahub.resource;
+package com.atomgraph.linkeddatahub.server.model.impl;
import com.atomgraph.client.util.HTMLMediaTypePredicate;
import com.atomgraph.client.vocabulary.AC;
import com.atomgraph.core.MediaTypes;
import com.atomgraph.core.model.EndpointAccessor;
+import com.atomgraph.core.riot.lang.RDFPostReader;
import com.atomgraph.linkeddatahub.apps.model.EndUserApplication;
import com.atomgraph.linkeddatahub.client.GraphStoreClient;
import com.atomgraph.linkeddatahub.model.CSVImport;
@@ -27,7 +28,6 @@
import com.atomgraph.linkeddatahub.model.Service;
import com.atomgraph.linkeddatahub.server.io.ValidatingModelProvider;
import com.atomgraph.linkeddatahub.server.model.Patchable;
-import com.atomgraph.linkeddatahub.server.model.impl.GraphStoreImpl;
import com.atomgraph.linkeddatahub.server.security.AgentContext;
import com.atomgraph.linkeddatahub.server.util.PatchUpdateVisitor;
import com.atomgraph.linkeddatahub.server.util.Skolemizer;
@@ -38,16 +38,15 @@
import com.atomgraph.linkeddatahub.vocabulary.SIOC;
import static com.atomgraph.server.status.UnprocessableEntityStatus.UNPROCESSABLE_ENTITY;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
+import java.net.URISyntaxException;
+import java.security.MessageDigest;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.NotFoundException;
@@ -55,16 +54,15 @@
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
-import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.Response.ResponseBuilder;
import static jakarta.ws.rs.core.Response.Status.PERMANENT_REDIRECT;
import jakarta.ws.rs.core.SecurityContext;
+import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Providers;
@@ -73,15 +71,18 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URISyntaxException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
import java.security.DigestInputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.codec.binary.Hex;
@@ -109,45 +110,83 @@
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.vocabulary.DCTerms;
import org.apache.jena.vocabulary.RDF;
+import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * JAX-RS resource that handles requests to directly-identified named graphs.
- * Direct identification is specified in the Graph Store Protocol.
+ * LinkedDataHub Graph Store implementation.
+ * We need to subclass the Core class because we're injecting a subclass of Service.
*
- * @author {@literal Martynas Jusevičius }
+ * @author Martynas Jusevičius {@literal }
*/
-public class Graph extends GraphStoreImpl implements Patchable
+public class DirectGraphStoreImpl extends com.atomgraph.core.model.impl.DirectGraphStoreImpl implements Patchable
{
- private static final Logger log = LoggerFactory.getLogger(Graph.class);
+ private static final Logger log = LoggerFactory.getLogger(DirectGraphStoreImpl.class);
- private final Set allowedMethods;
+ /**
+ * The relative path of the content-addressed file container.
+ */
+ public static final String UPLOADS_PATH = "uploads";
+ private final com.atomgraph.linkeddatahub.apps.model.Application application;
+ private final Ontology ontology;
+ private final Service service;
+ private final Providers providers;
+ private final com.atomgraph.linkeddatahub.Application system;
+ private final UriBuilder uploadsUriBuilder;
+ private final MessageDigest messageDigest;
+ /** The URIs for owner and secretary documents. */
+ protected final URI ownerDocURI, secretaryDocURI;
+ private final SecurityContext securityContext;
+ private final Optional agentContext;
+ private final Set allowedMethods;
+
/**
- * Constructs resource.
+ * Constructs Graph Store.
*
* @param request current request
- * @param uriInfo URI information of the current request
+ * @param uriInfo URI info of the current request
* @param mediaTypes a registry of readable/writable media types
* @param application current application
* @param ontology ontology of the current application
* @param service SPARQL service of the current application
* @param securityContext JAX-RS security context
* @param agentContext authenticated agent's context
- * @param providers JAX-RS provider registry
+ * @param providers registry of JAX-RS providers
* @param system system application
*/
@Inject
- public Graph(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
+ public DirectGraphStoreImpl(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
@Context SecurityContext securityContext, Optional agentContext,
@Context Providers providers, com.atomgraph.linkeddatahub.Application system)
{
- super(request, uriInfo, mediaTypes, application, ontology, service, securityContext, agentContext, providers, system);
+ super(request, service.get(), mediaTypes, uriInfo);
+ if (ontology.isEmpty()) throw new InternalServerErrorException("Ontology is not specified");
+ if (service.isEmpty()) throw new InternalServerErrorException("Service is not specified");
+ this.application = application;
+ this.ontology = ontology.get();
+ this.service = service.get();
+ this.securityContext = securityContext;
+ this.agentContext = agentContext;
+ this.providers = providers;
+ this.system = system;
+ this.messageDigest = system.getMessageDigest();
+ uploadsUriBuilder = uriInfo.getBaseUriBuilder().path(UPLOADS_PATH);
+ URI ownerURI = URI.create(application.getMaker().getURI());
+ try
+ {
+ this.ownerDocURI = new URI(ownerURI.getScheme(), ownerURI.getSchemeSpecificPart(), null).normalize();
+ this.secretaryDocURI = new URI(system.getSecretaryWebIDURI().getScheme(), system.getSecretaryWebIDURI().getSchemeSpecificPart(), null).normalize();
+ }
+ catch (URISyntaxException ex)
+ {
+ throw new InternalServerErrorException(ex);
+ }
URI uri = uriInfo.getAbsolutePath();
allowedMethods = new HashSet<>();
@@ -163,23 +202,23 @@ public Graph(@Context Request request, @Context UriInfo uriInfo, MediaTypes medi
!secretaryDocURI.equals(uri))
allowedMethods.add(HttpMethod.DELETE);
}
-
- @Override
- @GET
- public Response get(@QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUriUnused)
- {
- return super.get(false, getURI());
- }
+ /**
+ * Implements POST method of SPARQL Graph Store Protocol.
+ * Adds triples to the existing graph, skolemizes blank nodes, updates modification timestamp, and submits any imports.
+ *
+ * @param model RDF model to add to the graph
+ * @return HTTP response with updated entity tag
+ */
@Override
@POST
- public Response post(Model model, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUriUnused)
+ public Response post(Model model)
{
if (log.isTraceEnabled()) log.trace("POST Graph Store request with RDF payload: {} payload size(): {}", model, model.size());
final Model existingModel = getService().getGraphStoreClient().getModel(getURI().toString());
- ResponseBuilder rb = evaluatePreconditions(existingModel);
+ Response.ResponseBuilder rb = evaluatePreconditions(existingModel);
if (rb != null) return rb.build(); // preconditions not met
model.createResource(getURI().toString()).
@@ -203,10 +242,18 @@ public Response post(Model model, @QueryParam("default") @DefaultValue("false")
build();
}
+ /**
+ * Implements PUT method of SPARQL Graph Store Protocol.
+ * Creates a new graph or updates an existing one. Enforces trailing slash in URIs, skolemizes blank nodes,
+ * establishes parent/container relationships, and manages metadata (created, modified, creator, owner timestamps).
+ *
+ * @param model RDF model to create or update
+ * @return HTTP response with 201 Created for new graphs or 200 OK for updates
+ */
@Override
@PUT
// the AuthorizationFilter only allows creating new child URIs for existing containers (i.e. there has to be a .. container already)
- public Response put(Model model, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUriUnused)
+ public Response put(Model model)
{
if (log.isTraceEnabled()) log.trace("PUT Graph Store request with RDF payload: {} payload size(): {}", model, model.size());
@@ -239,7 +286,7 @@ public Response put(Model model, @QueryParam("default") @DefaultValue("false") B
{
existingModel = getService().getGraphStoreClient().getModel(getURI().toString());
- ResponseBuilder rb = evaluatePreconditions(existingModel);
+ Response.ResponseBuilder rb = evaluatePreconditions(existingModel);
if (rb != null) return rb.build(); // preconditions not met
}
catch (NotFoundException ex)
@@ -311,12 +358,11 @@ public Response put(Model model, @QueryParam("default") @DefaultValue("false") B
* The GRAPH keyword is therefore not allowed in the update string.
*
* @param updateRequest SPARQL update
- * @param graphUriUnused named graph URI (unused)
* @return response response object
*/
@PATCH
@Override
- public Response patch(UpdateRequest updateRequest, @QueryParam("graph") URI graphUriUnused)
+ public Response patch(UpdateRequest updateRequest)
{
if (updateRequest == null) throw new BadRequestException("SPARQL update not specified");
if (log.isDebugEnabled()) log.debug("PATCH request on named graph with URI: {}", getURI());
@@ -343,7 +389,7 @@ public Response patch(UpdateRequest updateRequest, @QueryParam("graph") URI grap
final Model existingModel = getService().getGraphStoreClient().getModel(getURI().toString());
if (existingModel == null) throw new NotFoundException("Named graph with URI <" + getURI() + "> not found");
- ResponseBuilder rb = evaluatePreconditions(existingModel);
+ Response.ResponseBuilder rb = evaluatePreconditions(existingModel);
if (rb != null) return rb.build(); // preconditions not met
Model beforeUpdateModel = ModelFactory.createDefaultModel().add(existingModel);
@@ -358,7 +404,7 @@ public Response patch(UpdateRequest updateRequest, @QueryParam("graph") URI grap
changedModel.add(existingModel.listStatements(resource, null, (RDFNode) null));
// if PATCH results in an empty model, treat it as a DELETE request
- if (changedModel.isEmpty()) return delete(Boolean.FALSE, getURI());
+ if (changedModel.isEmpty()) return delete();
validate(changedModel); // this would normally be done transparently by the ValidatingModelProvider
put(dataset.getDefaultModel(), Boolean.FALSE, getURI());
@@ -371,32 +417,6 @@ public Response patch(UpdateRequest updateRequest, @QueryParam("graph") URI grap
build();
}
- /**
- * Gets a diff of triples between two models and returns a set of their subject resources.
- *
- * @param beforeUpdateModel model before the update
- * @param afterUpdateModel model after the update
- * @return set of changed resources
- */
- public Set getChangedResources(Model beforeUpdateModel, Model afterUpdateModel)
- {
- if (beforeUpdateModel == null) throw new IllegalArgumentException("Model before update cannot be null");
- if (afterUpdateModel == null) throw new IllegalArgumentException("Model after update cannot be null");
-
- Model addedTriples = afterUpdateModel.difference(beforeUpdateModel);
- Model removedTriples = beforeUpdateModel.difference(afterUpdateModel);
-
- Set changedResources = new HashSet<>();
- addedTriples.listStatements().forEachRemaining(statement -> {
- changedResources.add(statement.getSubject());
- });
- removedTriples.listStatements().forEachRemaining(statement -> {
- changedResources.add(statement.getSubject());
- });
-
- return changedResources;
- }
-
/**
* Overrides OPTIONS HTTP header values.Specifies allowed methods.
*
@@ -420,13 +440,11 @@ public Response options()
* Files are written to storage before the RDF data is passed to the default POST handler method.
*
* @param multiPart multipart form data
- * @param defaultGraph true if default graph is requested
- * @param graphUriUnused named graph URI (unused)
* @return HTTP response
*/
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
- public Response postMultipart(FormDataMultiPart multiPart, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUriUnused)
+ public Response postMultipart(FormDataMultiPart multiPart)
{
if (log.isDebugEnabled()) log.debug("MultiPart fields: {} body parts: {}", multiPart.getFields(), multiPart.getBodyParts());
@@ -464,13 +482,11 @@ public Response postMultipart(FormDataMultiPart multiPart, @QueryParam("default"
* Files are written to storage before the RDF data is passed to the default PUT handler method.
*
* @param multiPart multipart form data
- * @param defaultGraph true if default graph is requested
- * @param graphUriUnused named graph URI (unused)
* @return HTTP response
*/
@PUT
@Consumes(MediaType.MULTIPART_FORM_DATA)
- public Response putMultipart(FormDataMultiPart multiPart, @QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUriUnused)
+ public Response putMultipart(FormDataMultiPart multiPart)
{
if (log.isDebugEnabled()) log.debug("MultiPart fields: {} body parts: {}", multiPart.getFields(), multiPart.getBodyParts());
@@ -486,7 +502,7 @@ public Response putMultipart(FormDataMultiPart multiPart, @QueryParam("default")
int fileCount = writeFiles(model, getFileNameBodyPartMap(multiPart));
if (log.isDebugEnabled()) log.debug("# of files uploaded: {} ", fileCount);
- return put(model, defaultGraph, getURI()); // ignore the @QueryParam("graph") value
+ return put(model, false, getURI());
}
catch (URISyntaxException ex)
{
@@ -503,13 +519,11 @@ public Response putMultipart(FormDataMultiPart multiPart, @QueryParam("default")
/**
* Implements DELETE method of SPARQL Graph Store Protocol.
*
- * @param defaultGraph true if default graph is requested
- * @param graphUriUnused named graph URI (unused)
* @return response
*/
@DELETE
@Override
- public Response delete(@QueryParam("default") @DefaultValue("false") Boolean defaultGraph, @QueryParam("graph") URI graphUriUnused)
+ public Response delete()
{
if (!getAllowedMethods().contains(HttpMethod.DELETE))
throw new WebApplicationException("Cannot delete document", Response.status(Response.Status.METHOD_NOT_ALLOWED).allow(getAllowedMethods()).build());
@@ -518,7 +532,7 @@ public Response delete(@QueryParam("default") @DefaultValue("false") Boolean def
{
Model existingModel = getService().getGraphStoreClient().getModel(getURI().toString());
- ResponseBuilder rb = evaluatePreconditions(existingModel);
+ Response.ResponseBuilder rb = evaluatePreconditions(existingModel);
if (rb != null) return rb.build(); // preconditions not met
}
catch (NotFoundException ex)
@@ -529,6 +543,32 @@ public Response delete(@QueryParam("default") @DefaultValue("false") Boolean def
return super.delete(false, getURI());
}
+ /**
+ * Gets a diff of triples between two models and returns a set of their subject resources.
+ *
+ * @param beforeUpdateModel model before the update
+ * @param afterUpdateModel model after the update
+ * @return set of changed resources
+ */
+ public Set getChangedResources(Model beforeUpdateModel, Model afterUpdateModel)
+ {
+ if (beforeUpdateModel == null) throw new IllegalArgumentException("Model before update cannot be null");
+ if (afterUpdateModel == null) throw new IllegalArgumentException("Model after update cannot be null");
+
+ Model addedTriples = afterUpdateModel.difference(beforeUpdateModel);
+ Model removedTriples = beforeUpdateModel.difference(afterUpdateModel);
+
+ Set changedResources = new HashSet<>();
+ addedTriples.listStatements().forEachRemaining(statement -> {
+ changedResources.add(statement.getSubject());
+ });
+ removedTriples.listStatements().forEachRemaining(statement -> {
+ changedResources.add(statement.getSubject());
+ });
+
+ return changedResources;
+ }
+
/**
* Get internal response object.
*
@@ -556,22 +596,11 @@ public com.atomgraph.core.model.impl.Response getInternalResponse(Model model, U
* @return response builder
*/
@Override
- public ResponseBuilder getResponseBuilder(Model model, URI graphUri)
+ public Response.ResponseBuilder getResponseBuilder(Model model, URI graphUri)
{
return getInternalResponse(model, graphUri).getResponseBuilder();
}
- /**
- * List allowed HTTP methods for the current graph URI.
- * Exceptions apply to the application's Root document, owner's WebID document, and secretary's WebID document.
- *
- * @return list of HTTP methods
- */
- public Set getAllowedMethods()
- {
- return allowedMethods;
- }
-
/**
* Writes all files from the multipart RDF/POST request body.
*
@@ -860,19 +889,88 @@ public Model validate(Model model)
* @param model RDF model
* @return {@code jakarta.ws.rs.core.Response.ResponseBuilder} instance. null if preconditions are not met.
*/
- public ResponseBuilder evaluatePreconditions(Model model)
+ public Response.ResponseBuilder evaluatePreconditions(Model model)
{
return getInternalResponse(model, getURI()).evaluatePreconditions();
}
/**
- * Returns the named graph URI.
+ * Parses multipart RDF/POST request.
+ *
+ * @param multiPart multipart form data
+ * @return RDF graph
+ * @throws URISyntaxException thrown if there is a syntax error in RDF/POST data
+ * @see RDF/POST Encoding for RDF
+ */
+ public Model parseModel(FormDataMultiPart multiPart) throws URISyntaxException
+ {
+ if (multiPart == null) throw new IllegalArgumentException("FormDataMultiPart cannot be null");
+
+ List keys = new ArrayList<>(), values = new ArrayList<>();
+ Iterator it = multiPart.getBodyParts().iterator(); // not using getFields() to retain ordering
+
+ while (it.hasNext())
+ {
+ FormDataBodyPart bodyPart = (FormDataBodyPart)it.next();
+ if (log.isDebugEnabled()) log.debug("Body part media type: {} headers: {}", bodyPart.getMediaType(), bodyPart.getHeaders());
+
+ // it's a file (if the filename is not empty)
+ if (bodyPart.getContentDisposition().getFileName() != null &&
+ !bodyPart.getContentDisposition().getFileName().isEmpty())
+ {
+ keys.add(bodyPart.getName());
+ if (log.isDebugEnabled()) log.debug("FormDataBodyPart name: {} value: {}", bodyPart.getName(), bodyPart.getContentDisposition().getFileName());
+ values.add(bodyPart.getContentDisposition().getFileName());
+ }
+ else
+ {
+ if (bodyPart.isSimple() && !bodyPart.getValue().isEmpty())
+ {
+ keys.add(bodyPart.getName());
+ if (log.isDebugEnabled()) log.debug("FormDataBodyPart name: {} value: {}", bodyPart.getName(), bodyPart.getValue());
+ values.add(bodyPart.getValue());
+ }
+ }
+ }
+
+ return RDFPostReader.parse(keys, values);
+ }
+
+ /**
+ * Gets a map of file parts from multipart form data.
*
- * @return graph URI
+ * @param multiPart multipart form data
+ * @return map of file parts
*/
- public URI getURI()
+ public Map getFileNameBodyPartMap(FormDataMultiPart multiPart)
{
- return getUriInfo().getAbsolutePath();
+ if (multiPart == null) throw new IllegalArgumentException("FormDataMultiPart cannot be null");
+
+ Map fileNameBodyPartMap = new HashMap<>();
+ Iterator it = multiPart.getBodyParts().iterator(); // not using getFields() to retain ordering
+ while (it.hasNext())
+ {
+ FormDataBodyPart bodyPart = (FormDataBodyPart)it.next();
+ if (log.isDebugEnabled()) log.debug("Body part media type: {} headers: {}", bodyPart.getMediaType(), bodyPart.getHeaders());
+
+ if (bodyPart.getContentDisposition().getFileName() != null) // it's a file
+ {
+ if (log.isDebugEnabled()) log.debug("FormDataBodyPart name: {} value: {}", bodyPart.getName(), bodyPart.getContentDisposition().getFileName());
+ fileNameBodyPartMap.put(bodyPart.getContentDisposition().getFileName(), bodyPart);
+ }
+ }
+ return fileNameBodyPartMap;
+ }
+
+ /**
+ * List allowed HTTP methods for the current graph URI.
+ * Exceptions apply to the application's Root document, owner's WebID document, and secretary's WebID document.
+ *
+ * @return list of HTTP methods
+ */
+ public Set getAllowedMethods()
+ {
+ return allowedMethods;
}
/**
@@ -885,4 +983,125 @@ public EndpointAccessor getEndpointAccessor()
return getService().getEndpointAccessor();
}
-}
+ /**
+ * Returns a list of supported languages.
+ *
+ * @return list of languages
+ */
+ @Override
+ public List getLanguages()
+ {
+ return getSystem().getSupportedLanguages();
+ }
+
+ /**
+ * Returns URI builder for uploaded file resources.
+ *
+ * @return URI builder
+ */
+ public UriBuilder getUploadsUriBuilder()
+ {
+ return uploadsUriBuilder.clone();
+ }
+
+ /**
+ * Returns message digest used in SHA1 hashing.
+ *
+ * @return message digest
+ */
+ public MessageDigest getMessageDigest()
+ {
+ return messageDigest;
+ }
+
+ /**
+ * Returns the current application.
+ *
+ * @return application resource
+ */
+ public com.atomgraph.linkeddatahub.apps.model.Application getApplication()
+ {
+ return application;
+ }
+
+ /**
+ * Returns the ontology of the current application.
+ *
+ * @return ontology resource
+ */
+ public Ontology getOntology()
+ {
+ return ontology;
+ }
+
+ /**
+ * Returns the SPARQL service of the current application.
+ *
+ * @return service resource
+ */
+ public Service getService()
+ {
+ return service;
+ }
+
+ /**
+ * Get JAX-RS security context
+ *
+ * @return security context object
+ */
+ public SecurityContext getSecurityContext()
+ {
+ return securityContext;
+ }
+
+ /**
+ * Gets authenticated agent's context
+ *
+ * @return optional agent's context
+ */
+ public Optional getAgentContext()
+ {
+ return agentContext;
+ }
+
+ /**
+ * Returns a registry of JAX-RS providers.
+ *
+ * @return provider registry
+ */
+ public Providers getProviders()
+ {
+ return providers;
+ }
+
+ /**
+ * Returns the system application.
+ *
+ * @return JAX-RS application
+ */
+ public com.atomgraph.linkeddatahub.Application getSystem()
+ {
+ return system;
+ }
+
+ /**
+ * Returns URI of the WebID document of the applications owner.
+ *
+ * @return document URI
+ */
+ public URI getOwnerDocURI()
+ {
+ return ownerDocURI;
+ }
+
+ /**
+ * Returns URI of the WebID document of the applications secretary.
+ *
+ * @return document URI
+ */
+ public URI getSecretaryDocURI()
+ {
+ return secretaryDocURI;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/Dispatcher.java b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/Dispatcher.java
index 451dc874f..1ad8f8002 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/Dispatcher.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/Dispatcher.java
@@ -27,7 +27,6 @@
import com.atomgraph.linkeddatahub.resource.admin.pkg.UninstallPackage;
import com.atomgraph.linkeddatahub.resource.Settings;
import com.atomgraph.linkeddatahub.resource.admin.SignUp;
-import com.atomgraph.linkeddatahub.resource.Graph;
import com.atomgraph.linkeddatahub.resource.acl.Access;
import com.atomgraph.linkeddatahub.resource.acl.AccessRequest;
import java.util.Optional;
@@ -82,12 +81,12 @@ public Optional getProxyClass()
if (getUriInfo().getQueryParameters().containsKey(AC.uri.getLocalName()))
{
if (log.isDebugEnabled()) log.debug("No Application matched request URI <{}>, dispatching to ProxyResourceBase", getUriInfo().getQueryParameters().getFirst(AC.uri.getLocalName()));
- return Optional.of(ProxyResourceBase.class);
+ return Optional.of(ProxiedGraph.class);
}
if (getDataset().isPresent())
{
if (log.isDebugEnabled()) log.debug("Serving request URI <{}> from Dataset <{}>, dispatching to ProxyResourceBase", getUriInfo().getAbsolutePath(), getDataset().get());
- return Optional.of(ProxyResourceBase.class);
+ return Optional.of(ProxiedGraph.class);
}
return Optional.empty();
@@ -181,7 +180,7 @@ public Class getAccessRequest()
@Path("uploads/{sha1sum}")
public Class getFileItem()
{
- return getProxyClass().orElse(com.atomgraph.linkeddatahub.resource.upload.sha1.Item.class);
+ return getProxyClass().orElse(com.atomgraph.linkeddatahub.resource.upload.Item.class);
}
/**
@@ -263,12 +262,13 @@ public Class getSettingsEndpoint()
/**
* Returns the default JAX-RS resource class.
+ * Only directly identified access to named graphs is allowed (the Graph Store Protocol endpoint is not exposed).
*
* @return resource class
*/
public Class getDocumentClass()
{
- return Graph.class;
+ return DirectGraphStoreImpl.class;
}
/**
diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/GraphStoreImpl.java b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/GraphStoreImpl.java
deleted file mode 100644
index 1c25cd2a2..000000000
--- a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/GraphStoreImpl.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/**
- * Copyright 2019 Martynas Jusevičius
- *
- * Licensed 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 com.atomgraph.linkeddatahub.server.model.impl;
-
-import com.atomgraph.core.MediaTypes;
-import com.atomgraph.core.riot.lang.RDFPostReader;
-import com.atomgraph.linkeddatahub.model.Service;
-import com.atomgraph.linkeddatahub.server.security.AgentContext;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.MessageDigest;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.InternalServerErrorException;
-import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.Request;
-import jakarta.ws.rs.core.SecurityContext;
-import jakarta.ws.rs.core.UriBuilder;
-import jakarta.ws.rs.core.UriInfo;
-import jakarta.ws.rs.ext.Providers;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import org.apache.jena.ontology.Ontology;
-import org.apache.jena.rdf.model.Model;
-import org.glassfish.jersey.media.multipart.BodyPart;
-import org.glassfish.jersey.media.multipart.FormDataBodyPart;
-import org.glassfish.jersey.media.multipart.FormDataMultiPart;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * LinkedDataHub Graph Store implementation.
- * We need to subclass the Core class because we're injecting a subclass of Service.
- *
- * @author Martynas Jusevičius {@literal }
- */
-public abstract class GraphStoreImpl extends com.atomgraph.core.model.impl.GraphStoreImpl
-{
-
- private static final Logger log = LoggerFactory.getLogger(GraphStoreImpl.class);
-
- /**
- * The relative path of the content-addressed file container.
- */
- public static final String UPLOADS_PATH = "uploads";
-
- private final UriInfo uriInfo;
- private final com.atomgraph.linkeddatahub.apps.model.Application application;
- private final Ontology ontology;
- private final Service service;
- private final Providers providers;
- private final com.atomgraph.linkeddatahub.Application system;
- private final UriBuilder uploadsUriBuilder;
- private final MessageDigest messageDigest;
- /** The URIs for owner and secretary documents. */
- protected final URI ownerDocURI, secretaryDocURI;
- private final SecurityContext securityContext;
- private final Optional agentContext;
-
- /**
- * Constructs Graph Store.
- *
- * @param request current request
- * @param uriInfo URI info of the current request
- * @param mediaTypes a registry of readable/writable media types
- * @param application current application
- * @param ontology ontology of the current application
- * @param service SPARQL service of the current application
- * @param securityContext JAX-RS security context
- * @param agentContext authenticated agent's context
- * @param providers registry of JAX-RS providers
- * @param system system application
- */
- @Inject
- public GraphStoreImpl(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes,
- com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service,
- @Context SecurityContext securityContext, Optional agentContext,
- @Context Providers providers, com.atomgraph.linkeddatahub.Application system)
- {
- super(request, service.get(), mediaTypes);
- if (ontology.isEmpty()) throw new InternalServerErrorException("Ontology is not specified");
- if (service.isEmpty()) throw new InternalServerErrorException("Service is not specified");
- this.uriInfo = uriInfo;
- this.application = application;
- this.ontology = ontology.get();
- this.service = service.get();
- this.securityContext = securityContext;
- this.agentContext = agentContext;
- this.providers = providers;
- this.system = system;
- this.messageDigest = system.getMessageDigest();
- uploadsUriBuilder = uriInfo.getBaseUriBuilder().path(UPLOADS_PATH);
- URI ownerURI = URI.create(application.getMaker().getURI());
- try
- {
- this.ownerDocURI = new URI(ownerURI.getScheme(), ownerURI.getSchemeSpecificPart(), null).normalize();
- this.secretaryDocURI = new URI(system.getSecretaryWebIDURI().getScheme(), system.getSecretaryWebIDURI().getSchemeSpecificPart(), null).normalize();
- }
- catch (URISyntaxException ex)
- {
- throw new InternalServerErrorException(ex);
- }
- }
-
- /**
- * Parses multipart RDF/POST request.
- *
- * @param multiPart multipart form data
- * @return RDF graph
- * @throws URISyntaxException thrown if there is a syntax error in RDF/POST data
- * @see RDF/POST Encoding for RDF
- */
- public Model parseModel(FormDataMultiPart multiPart) throws URISyntaxException
- {
- if (multiPart == null) throw new IllegalArgumentException("FormDataMultiPart cannot be null");
-
- List keys = new ArrayList<>(), values = new ArrayList<>();
- Iterator it = multiPart.getBodyParts().iterator(); // not using getFields() to retain ordering
-
- while (it.hasNext())
- {
- FormDataBodyPart bodyPart = (FormDataBodyPart)it.next();
- if (log.isDebugEnabled()) log.debug("Body part media type: {} headers: {}", bodyPart.getMediaType(), bodyPart.getHeaders());
-
- // it's a file (if the filename is not empty)
- if (bodyPart.getContentDisposition().getFileName() != null &&
- !bodyPart.getContentDisposition().getFileName().isEmpty())
- {
- keys.add(bodyPart.getName());
- if (log.isDebugEnabled()) log.debug("FormDataBodyPart name: {} value: {}", bodyPart.getName(), bodyPart.getContentDisposition().getFileName());
- values.add(bodyPart.getContentDisposition().getFileName());
- }
- else
- {
- if (bodyPart.isSimple() && !bodyPart.getValue().isEmpty())
- {
- keys.add(bodyPart.getName());
- if (log.isDebugEnabled()) log.debug("FormDataBodyPart name: {} value: {}", bodyPart.getName(), bodyPart.getValue());
- values.add(bodyPart.getValue());
- }
- }
- }
-
- return RDFPostReader.parse(keys, values);
- }
-
- /**
- * Gets a map of file parts from multipart form data.
- *
- * @param multiPart multipart form data
- * @return map of file parts
- */
- public Map getFileNameBodyPartMap(FormDataMultiPart multiPart)
- {
- if (multiPart == null) throw new IllegalArgumentException("FormDataMultiPart cannot be null");
-
- Map fileNameBodyPartMap = new HashMap<>();
- Iterator it = multiPart.getBodyParts().iterator(); // not using getFields() to retain ordering
- while (it.hasNext())
- {
- FormDataBodyPart bodyPart = (FormDataBodyPart)it.next();
- if (log.isDebugEnabled()) log.debug("Body part media type: {} headers: {}", bodyPart.getMediaType(), bodyPart.getHeaders());
-
- if (bodyPart.getContentDisposition().getFileName() != null) // it's a file
- {
- if (log.isDebugEnabled()) log.debug("FormDataBodyPart name: {} value: {}", bodyPart.getName(), bodyPart.getContentDisposition().getFileName());
- fileNameBodyPartMap.put(bodyPart.getContentDisposition().getFileName(), bodyPart);
- }
- }
- return fileNameBodyPartMap;
- }
-
- /**
- * Returns a list of supported languages.
- *
- * @return list of languages
- */
- @Override
- public List getLanguages()
- {
- return getSystem().getSupportedLanguages();
- }
-
- /**
- * Returns URI builder for uploaded file resources.
- *
- * @return URI builder
- */
- public UriBuilder getUploadsUriBuilder()
- {
- return uploadsUriBuilder.clone();
- }
-
- /**
- * Returns message digest used in SHA1 hashing.
- *
- * @return message digest
- */
- public MessageDigest getMessageDigest()
- {
- return messageDigest;
- }
-
- /**
- * Returns the request URI information.
- *
- * @return URI info
- */
- public UriInfo getUriInfo()
- {
- return uriInfo;
- }
-
- /**
- * Returns the current application.
- *
- * @return application resource
- */
- public com.atomgraph.linkeddatahub.apps.model.Application getApplication()
- {
- return application;
- }
-
- /**
- * Returns the ontology of the current application.
- *
- * @return ontology resource
- */
- public Ontology getOntology()
- {
- return ontology;
- }
-
- /**
- * Returns the SPARQL service of the current application.
- *
- * @return service resource
- */
- public Service getService()
- {
- return service;
- }
-
- /**
- * Get JAX-RS security context
- *
- * @return security context object
- */
- public SecurityContext getSecurityContext()
- {
- return securityContext;
- }
-
- /**
- * Gets authenticated agent's context
- *
- * @return optional agent's context
- */
- public Optional getAgentContext()
- {
- return agentContext;
- }
-
- /**
- * Returns a registry of JAX-RS providers.
- *
- * @return provider registry
- */
- public Providers getProviders()
- {
- return providers;
- }
-
- /**
- * Returns the system application.
- *
- * @return JAX-RS application
- */
- public com.atomgraph.linkeddatahub.Application getSystem()
- {
- return system;
- }
-
- /**
- * Returns URI of the WebID document of the applications owner.
- *
- * @return document URI
- */
- public URI getOwnerDocURI()
- {
- return ownerDocURI;
- }
-
- /**
- * Returns URI of the WebID document of the applications secretary.
- *
- * @return document URI
- */
- public URI getSecretaryDocURI()
- {
- return secretaryDocURI;
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBase.java b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/ProxiedGraph.java
similarity index 98%
rename from src/main/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBase.java
rename to src/main/java/com/atomgraph/linkeddatahub/server/model/impl/ProxiedGraph.java
index 5665a8e72..02eb59f62 100644
--- a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBase.java
+++ b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/ProxiedGraph.java
@@ -83,10 +83,10 @@
*
* @author {@literal Martynas Jusevičius }
*/
-public class ProxyResourceBase extends com.atomgraph.client.model.impl.ProxyResourceBase
+public class ProxiedGraph extends com.atomgraph.client.model.impl.ProxiedGraph
{
- private static final Logger log = LoggerFactory.getLogger(ProxyResourceBase.class);
+ private static final Logger log = LoggerFactory.getLogger(ProxiedGraph.class);
private final UriInfo uriInfo;
private final ContainerRequestContext crc;
@@ -117,7 +117,7 @@ public class ProxyResourceBase extends com.atomgraph.client.model.impl.ProxyReso
* @param dataset optional dataset
*/
@Inject
- public ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders httpHeaders, MediaTypes mediaTypes,
+ public ProxiedGraph(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders httpHeaders, MediaTypes mediaTypes,
com.atomgraph.linkeddatahub.apps.model.Application application, Optional service,
@Context SecurityContext securityContext, @Context ContainerRequestContext crc,
com.atomgraph.linkeddatahub.Application system, @Context HttpServletRequest httpServletRequest, DataManager dataManager, Optional agentContext,
@@ -157,7 +157,7 @@ public ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Co
* @param agentContext authenticated agent's context
* @param providers registry of JAX-RS providers
*/
- protected ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders httpHeaders, MediaTypes mediaTypes,
+ protected ProxiedGraph(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders httpHeaders, MediaTypes mediaTypes,
com.atomgraph.linkeddatahub.apps.model.Application application, Optional service,
@Context SecurityContext securityContext, @Context ContainerRequestContext crc,
@QueryParam("uri") URI uri, @QueryParam("endpoint") URI endpoint, @QueryParam("query") String query, @QueryParam("accept") MediaType accept, @QueryParam("mode") URI mode,
diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBaseTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBaseTest.java
index 7e9cc8c43..4e8687796 100644
--- a/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBaseTest.java
+++ b/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/ProxyResourceBaseTest.java
@@ -33,51 +33,51 @@ public class ProxyResourceBaseTest
@Test(expected = IllegalArgumentException.class)
public void testNullURI()
{
- ProxyResourceBase.validateNotInternalURL(null);
+ ProxiedGraph.validateNotInternalURL(null);
}
@Test(expected = ForbiddenException.class)
public void testLinkLocalIPv4Blocked()
{
- ProxyResourceBase.validateNotInternalURL(URI.create("http://169.254.1.1:8080/test"));
+ ProxiedGraph.validateNotInternalURL(URI.create("http://169.254.1.1:8080/test"));
}
@Test(expected = ForbiddenException.class)
public void testPrivateClass10Blocked()
{
- ProxyResourceBase.validateNotInternalURL(URI.create("http://10.0.0.1:8080/test"));
+ ProxiedGraph.validateNotInternalURL(URI.create("http://10.0.0.1:8080/test"));
}
@Test(expected = ForbiddenException.class)
public void testPrivateClass172Blocked()
{
- ProxyResourceBase.validateNotInternalURL(URI.create("http://172.16.0.0:8080/test"));
+ ProxiedGraph.validateNotInternalURL(URI.create("http://172.16.0.0:8080/test"));
}
@Test(expected = ForbiddenException.class)
public void testPrivateClass192Blocked()
{
- ProxyResourceBase.validateNotInternalURL(URI.create("http://192.168.1.1:8080/test"));
+ ProxiedGraph.validateNotInternalURL(URI.create("http://192.168.1.1:8080/test"));
}
@Test
public void testExternalURLAllowed()
{
// Public IPs should be allowed (no exception thrown)
- ProxyResourceBase.validateNotInternalURL(URI.create("http://8.8.8.8:80/test"));
+ ProxiedGraph.validateNotInternalURL(URI.create("http://8.8.8.8:80/test"));
}
@Test
public void testPublicDomainAllowed()
{
// Public domains should be allowed (no exception thrown)
- ProxyResourceBase.validateNotInternalURL(URI.create("http://example.org/test"));
+ ProxiedGraph.validateNotInternalURL(URI.create("http://example.org/test"));
}
@Test
public void testHTTPSAllowed()
{
// HTTPS to public domain should be allowed (no exception thrown)
- ProxyResourceBase.validateNotInternalURL(URI.create("https://www.w3.org/ns/ldp"));
+ ProxiedGraph.validateNotInternalURL(URI.create("https://www.w3.org/ns/ldp"));
}
}