This guide explains the functionalities of the jelly-jena module, which provides Jelly support for Apache Jena.
If you just want to add Jelly format support to Apache Jena / Apache Jena Fuseki, you can use the Jelly-JVM plugin JAR. See the dedicated guide for more information.
jelly-jena implements an RDF writer and reader for Apache Jena's RIOT library. This means you can use Jelly just like, for example, Turtle or RDF/XML. See the example below:
packageeu.ostrzyciel.jelly.examplesimporteu.ostrzyciel.jelly.convert.jena.riot.*importeu.ostrzyciel.jelly.core.*importorg.apache.jena.rdf.model.ModelFactoryimportorg.apache.jena.riot.{RDFDataMgr,RDFFormat,RDFParser,RDFWriterRegistry,RIOT}importjava.io.{File,FileOutputStream}importscala.util.Using/** * Example of using Jelly's integration with Apache Jena's RIOT library for * writing and reading RDF graphs and datasets to/from disk. * * See also: https://jena.apache.org/documentation/io/ */objectJenaRiotextendsshared.Example:defmain(args:Array[String]):Unit=// Load the RDF graph from an N-Triples filevalmodel=RDFDataMgr.loadModel(File(getClass.getResource("/weather.nt").toURI).toURI.toString)// Print the size of the modelprintln(s"Loaded an RDF graph from N-Triples with size: ${model.size}")Using.resource(newFileOutputStream("weather.jelly")){out=>// Write the model to a Jelly file// Note: by default this will use the [[JellyFormat.JELLY_SMALL_STRICT]] format variantRDFDataMgr.write(out,model,JellyLanguage.JELLY)println("Saved the model to a Jelly file")}// Load the RDF graph from a Jelly filevalmodel2=RDFDataMgr.loadModel("weather.jelly",JellyLanguage.JELLY)// Print the size of the modelprintln(s"Loaded an RDF graph from Jelly with size: ${model2.size}")// ---------------------------------println("\n")// Try the same with an RDF dataset and some different settingsvaldataset=RDFDataMgr.loadDataset(File(getClass.getResource("/weather-graphs.trig").toURI).toURI.toString)println(s"Loaded an RDF dataset from a Trig file with ${dataset.asDatasetGraph.size} named graphs and "+s"${dataset.asDatasetGraph.stream.count} quads")Using.resource(newFileOutputStream("weather-quads.jelly")){out=>// Write the dataset to a Jelly file, using the "BIG" settings// (better compression for big files, more memory usage)RDFDataMgr.write(out,dataset,JellyFormat.JELLY_BIG_STRICT)println("Saved the dataset to a Jelly file")}// Load the RDF dataset from a Jelly filevaldataset2=RDFDataMgr.loadDataset("weather-quads.jelly",JellyLanguage.JELLY)println(s"Loaded an RDF dataset from Jelly with ${dataset2.asDatasetGraph.size} named graphs and "+s"${dataset2.asDatasetGraph.stream.count} quads")// ---------------------------------println("\n")// Custom Jelly format – change any settings you likevalcustomFormat=newRDFFormat(JellyLanguage.JELLY,JellyFormatVariant(opt=JellyOptions.smallStrict.withMaxPrefixTableSize(0)// disable the prefix table.withStreamName("My weather stream"),// add metadata to the streamframeSize=16// make RdfStreamFrames with 16 rows each))// Jena requires us to register the custom format – once for graphs and once for datasets,// as Jelly supports both.RDFWriterRegistry.register(customFormat,JellyGraphWriterFactory)RDFWriterRegistry.register(customFormat,JellyDatasetWriterFactory)Using.resource(newFileOutputStream("weather-quads-custom.jelly")){out=>// Write the dataset to a Jelly file using the custom formatRDFDataMgr.write(out,dataset,customFormat)println("Saved the dataset to a Jelly file with custom settings")}// Load the RDF dataset from a Jelly file with the custom formatvaldataset3=RDFDataMgr.loadDataset("weather-quads-custom.jelly",JellyLanguage.JELLY)println(s"Loaded an RDF dataset from Jelly with custom settings with ${dataset3.asDatasetGraph.size} named graphs"+s" and ${dataset3.asDatasetGraph.stream.count} quads")// ---------------------------------println("\n")// By default, the parser has limits on for example the maximum size of the lookup tables.// The default supported options are [[JellyOptions.defaultSupportedOptions]].// You can change these limits by creating your own options object.valcustomOptions=JellyOptions.defaultSupportedOptions.withMaxNameTableSize(50)// set the maximum size of the name table to 100// Create a Context object with the custom optionsvalparserContext=RIOT.getContext.copy().set(JellyLanguage.SYMBOL_SUPPORTED_OPTIONS,customOptions)println("Trying to load the model with custom supported options...")valmodel3=ModelFactory.createDefaultModel()try// The loading operation should fail because our allowed max name table size is too lowRDFParser.create().source("weather.jelly").lang(JellyLanguage.JELLY)// Set the context object with the custom options.context(parserContext).parse(model3)catchcasee:RdfProtoDeserializationError=>// The stream uses a name table size of 128, which is larger than the maximum supported size of 50.// To read this stream, set maxNameTableSize to at least 128 in the supportedOptions for this decoder.println(s"Failed to load the model with custom options: ${e.getMessage}")
Usage notes:
eu.ostrzyciel.jelly.core.JellyOptions provides a few common presets for Jelly serialization options construct a JellyFormatVariant, as shown in the example above. You can also further customize the serialization options (e.g., dictionary size).
The RIOT writer (serializer) integration implements only the delimited variant of Jelly. It is used for writing Jelly to files on disk or sockets. Because of this, you cannot use RIOT to write non-delimited Jelly data (e.g., a single message to a Kafka stream). For this, you should use the jelly-stream module or the more low-level API: Low-level usage.
However, the RIOT parser (deserializer) integration will automatically detect if the parsed Jelly data is delimited or not. If it's non-delimited, the parser will assume that there is only one RdfStreamFrame in the file.
jelly-jena also implements a streaming writer (StreamRDF API in Jena). Using it is similar to the regular RIOT writer, with a slightly different setup:
Example: JenaRiotStreaming.scala (click to expand)
packageeu.ostrzyciel.jelly.examplesimporteu.ostrzyciel.jelly.convert.jena.riot.*importeu.ostrzyciel.jelly.core.JellyOptionsimporteu.ostrzyciel.jelly.core.proto.v1.PhysicalStreamTypeimportorg.apache.jena.graph.{NodeFactory,Triple}importorg.apache.jena.riot.system.{StreamRDFLib,StreamRDFWriter}importorg.apache.jena.riot.{RDFDataMgr,RDFParser,RIOT}importjava.io.{File,FileOutputStream}importscala.util.Using/** * Example of using Apache Jena's streaming IO API with Jelly. * * See also: https://jena.apache.org/documentation/io/streaming-io.html */objectJenaRiotStreamingextendsshared.Example:defmain(args:Array[String]):Unit=// Initialize a Jena StreamRDF to consume the statementsvalreaderStream=StreamRDFLib.count()println("Reading a stream of triples from a Jelly file...")// Parse a Jelly file as a stream of triplesvalinputFileTriples=newFile(getClass.getResource("/jelly/weather.jelly").toURI)RDFParser.source(inputFileTriples.toURI.toString).lang(JellyLanguage.JELLY).parse(readerStream)println(f"Read ${readerStream.countTriples()} triples")println()println("Reading a stream of quads from a Jelly file...")// Parse a different Jelly file as a stream of quads and send it to the same sinkvalinputFileQuads=newFile(getClass.getResource("/jelly/weather-quads.jelly").toURI)RDFParser.source(inputFileQuads.toURI.toString).lang(JellyLanguage.JELLY).parse(readerStream)// Print the number of triples and quads//// The number of triples here is the sum of the triples from the first file and the triples// in the default graph of the second file. This is just how Jena handles it.println(f"Read ${readerStream.countTriples()} triples (in total)"+f" and ${readerStream.countQuads()} quads")// -------------------------------------println("\n")println("Writing a stream of 10 triples to a file...")// Try writing some triples to a file// We need to create an instance of RdfStreamOptions to pass to the writer:valoptions=JellyOptions.smallStrict// The stream writer does not know if we will be writing triples or quads – we// have to specify the physical stream type explicitly..withPhysicalType(PhysicalStreamType.TRIPLES).withStreamName("A stream of 10 triples")// To pass the options, we use Jena's Context mechanismvalcontext=RIOT.getContext.copy().set(JellyLanguage.SYMBOL_STREAM_OPTIONS,options).set(JellyLanguage.SYMBOL_FRAME_SIZE,128)// optional, default is 256Using.resource(newFileOutputStream("stream-riot.jelly")){out=>// Create the writer – remember to pass the context!valwriterStream=StreamRDFWriter.getWriterStream(out,JellyLanguage.JELLY,context)writerStream.start()fori<-1to10dowriterStream.triple(Triple.create(NodeFactory.createBlankNode(),NodeFactory.createURI("https://example.org/p"),NodeFactory.createLiteralString(s"object $i")))writerStream.finish()}println("Done writing triples")// Load the RDF graph that we just saved using normal RIOT APIvalmodel=RDFDataMgr.loadModel("stream-riot.jelly",JellyLanguage.JELLY)println("Loaded the stream from disk, contents:\n")model.write(System.out,"NT")