Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Fuseki integration to a Jena module #179

Merged
merged 5 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions docs/docs/getting-started-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,9 @@ You can simply add Jelly format support to [Apache Jena](https://jena.apache.org
- For other applications, consult the manual of the application.
- You can now use the Jelly format for parsing, serialization, and streaming serialization in your Jena application.

!!! bug "Content negotiation in Fuseki"
!!! warning "Content negotiation in Fuseki"

Content negotiation using the `application/x-jelly-rdf` media type in the `Accept` header works in Fuseki "Main" distribution since version 5.2.0. To get it working, you need to run Fuseki with the `--modules=true` command-line option. Content negotiation does not work in the "webapp" distribution with UI due to an [upstream bug](https://github.com/apache/jena/issues/2774).

In Fuseki 5.1.0 and older, content negotiation with Jelly does not work at all.

To work around these issues, you can specify the `output=application/x-jelly-rdf` parameter (either in the URL or in the URL-encoded form body) when querying the endpoint.
Content negotiation using the `application/x-jelly-rdf` media type in the `Accept` header works in Fuseki since Apache Jena version 5.2.0. Previous versions of Fuseki did not support media type registration.


### Eclipse RDF4J
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
eu.ostrzyciel.jelly.convert.jena.riot.JellySubsystemLifecycle
eu.ostrzyciel.jelly.convert.jena.fuseki.JellyFusekiLifecycle
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,46 @@ package eu.ostrzyciel.jelly.convert.jena.fuseki
import eu.ostrzyciel.jelly.core.Constants
import org.apache.jena.atlas.web.{AcceptList, MediaRange}
import org.apache.jena.fuseki.{DEF, Fuseki}
import org.apache.jena.fuseki.main.FusekiServer
import org.apache.jena.fuseki.main.sys.FusekiAutoModule
import org.apache.jena.rdf.model.Model
import org.apache.jena.riot.WebContent
import org.apache.jena.sys.JenaSubsystemLifecycle

import java.util

object JellyFusekiModule:
object JellyFusekiLifecycle:
val mediaRangeJelly: MediaRange = new MediaRange(Constants.jellyContentType)

/**
* A Fuseki module that adds Jelly content type to the list of accepted content types.
* A Jena module that adds Jelly content type to the list of accepted content types in Fuseki.
* This isn't a Fuseki module, because Fuseki modules are not supported in all distributions of Fuseki, see:
* https://github.com/apache/jena/issues/2774
*
* This allows users to use the Accept header set to application/x-jelly-rdf to request Jelly RDF responses.
* It works for SPARQL CONSTRUCT queries and for the Graph Store Protocol.
*
* More info on Fuseki modules: https://jena.apache.org/documentation/fuseki2/fuseki-modules.html
*/
final class JellyFusekiModule extends FusekiAutoModule:
import JellyFusekiModule.*

override def name(): String = "Jelly"
final class JellyFusekiLifecycle extends JenaSubsystemLifecycle:
import JellyFusekiLifecycle.*

override def start(): Unit =
try {
maybeAddJellyToList(DEF.constructOffer).foreach(offer => DEF.constructOffer = offer)
maybeAddJellyToList(DEF.rdfOffer).foreach(offer => DEF.rdfOffer = offer)
maybeAddJellyToList(DEF.quadsOffer).foreach(offer => {
DEF.quadsOffer = offer
Fuseki.serverLog.info(s"Added ${Constants.jellyContentType} to the list of accepted content types")
Fuseki.serverLog.info(s"Jelly: Added ${Constants.jellyContentType} to the list of accepted content types")
})
} catch {
case e: NoClassDefFoundError => // ignore, we are not running Fuseki
case e: IllegalAccessError => Fuseki.serverLog.warn(
s"Cannot register the ${Constants.jellyContentType} content type, because you are running an Apache Jena " +
s"Fuseki version that doesn't support content type registration. " +
s"Jelly: Cannot register the ${Constants.jellyContentType} content type, because you are running an " +
s"Apache Jena Fuseki version that doesn't support content type registration. " +
s"Update to Fuseki 5.2.0 or newer for this to work."
)
}

override def stop(): Unit = ()

// Initialize after JellySubsystemLifecycle
override def level(): Int = 502

/**
* Adds the Jelly content type to the list of accepted content types if it is not already present.
* @param list current list of accepted content types
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package eu.ostrzyciel.jelly.convert.jena.fuseki

import eu.ostrzyciel.jelly.convert.jena.riot.JellySubsystemLifecycle
import org.apache.jena.fuseki.DEF
import org.apache.jena.sys.JenaSystem
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

import scala.jdk.CollectionConverters.*

class JellyFusekiLifecycleSpec extends AnyWordSpec, Matchers:
"JellyFusekiLifecycle" should {
"initialize after JenaSubsystemLifecycle" in {
val jenaModule = JellySubsystemLifecycle()
val module = JellyFusekiLifecycle()
module.level() should be > jenaModule.level()
}

"use the correct content type for Jelly" in {
JellyFusekiLifecycle.mediaRangeJelly.getContentTypeStr should be ("application/x-jelly-rdf")
}

"register the Jelly content type in the lists of accepted content types" in {
// This assumption is broken for `jenaPlugin` tests, because there the JenaSystem is initialized
// and reads the service definition.
// This test thus only can be executed in the `jena` module.
assume(DEF.constructOffer == DEF.constructOfferDefault())

val oldLists = List(DEF.constructOffer, DEF.rdfOffer, DEF.quadsOffer)
for list <- oldLists do
list.entries().asScala should not contain JellyFusekiLifecycle.mediaRangeJelly

val module = JellyFusekiLifecycle()
module.start()

val lists = List(DEF.constructOffer, DEF.rdfOffer, DEF.quadsOffer)
for (list, oldList) <- lists.zip(oldLists) do
list.entries().asScala should contain (JellyFusekiLifecycle.mediaRangeJelly)
list.entries().size() should be (oldList.entries().size() + 1)
}

"not register the Jelly content type if it's already registered" in {
val module = JellyFusekiLifecycle()
module.start()
DEF.rdfOffer.entries().asScala should contain (JellyFusekiLifecycle.mediaRangeJelly)
val size1 = DEF.rdfOffer.entries().size()

module.start()
DEF.rdfOffer.entries().asScala should contain (JellyFusekiLifecycle.mediaRangeJelly)
val size2 = DEF.rdfOffer.entries().size()
size2 should be (size1)
}
}

This file was deleted.