diff --git a/jena-arq/Grammar/RDF-Thrift/BinaryRDF.thrift b/jena-arq/Grammar/RDF-Thrift/BinaryRDF.thrift index f27c7ec0424..460b72e09b4 100644 --- a/jena-arq/Grammar/RDF-Thrift/BinaryRDF.thrift +++ b/jena-arq/Grammar/RDF-Thrift/BinaryRDF.thrift @@ -19,8 +19,6 @@ // Encoding in Thrift of RDF terms and other items // for Graph, Datasets, Result Set and Patches -// Versioning considerations? - namespace java org.apache.jena.riot.thrift.wire // ==== RDF Term Definitions @@ -42,7 +40,7 @@ struct RDF_BNode { 1: required string label } -// Common abbreviations for datatypes and other URIs? +// Common abbreviated for datatypes and other URIs? // union with additional values. struct RDF_Literal { @@ -77,7 +75,6 @@ union RDF_Term { 7: RDF_UNDEF undefined 8: RDF_REPEAT repeat 9: RDF_Triple tripleTerm # RDF-star - # Value forms of literals. 10: i64 valInteger 11: double valDouble @@ -122,17 +119,48 @@ struct RDF_DataTuple { 1: list row } -// // ==== RDF Patch -// -// # Includes -// # Prefix declaration -// -// enum RDF_Patch { -// ADD, -// ADD_NO_OP, // ADD recorded that had no effect -// DELETE, -// DELETE_NO_OP // DELETE recorded that had no effect -// } +// ==== RDF Patch + +enum PatchTxn { TX, TC, TA , Segment } + +struct Patch_Prefix_Add { +1: optional RDF_Term graphNode; +2: required string prefix; +3: required string iriStr; +} + +struct Patch_Prefix_Del { +1: optional RDF_Term graphNode; +2: required string prefix; +} + +struct Patch_Header { +1: required string name; +2: required RDF_Term value; +} + +struct Patch_Data_Add { +1: required RDF_Term s; +2: required RDF_Term p; +3: required RDF_Term o; +4: optional RDF_Term g; +} + +struct Patch_Data_Del { +1: required RDF_Term s; +2: required RDF_Term p; +3: required RDF_Term o; +4: optional RDF_Term g; +} + +union RDF_Patch_Row { +1: Patch_Header header; +2: Patch_Data_Add dataAdd; +3: Patch_Data_Del dataDel; +4: Patch_Prefix_Add prefixAdd; +5: Patch_Prefix_Del prefixDel; +6: PatchTxn txn; +} // Local Variables: // tab-width: 2 diff --git a/jena-arq/Grammar/RDF-Thrift/gen-thrift b/jena-arq/Grammar/RDF-Thrift/gen-thrift index 19b0c607f78..0e402ac80b0 100755 --- a/jena-arq/Grammar/RDF-Thrift/gen-thrift +++ b/jena-arq/Grammar/RDF-Thrift/gen-thrift @@ -23,3 +23,11 @@ do perl -i.bak -p -e 's/^\@SuppressWarnings.*$/\@SuppressWarnings("all")/' $f rm -f $f.bak done + +## PatchTxn.java +F="$PKG/PatchTxn.java" +if [ -e "$F" ] +then + sed -e 's/public int getValue/@Override public int getValue/' < $F > F + mv F $F +fi diff --git a/jena-arq/src/main/java/org/apache/jena/riot/WebContent.java b/jena-arq/src/main/java/org/apache/jena/riot/WebContent.java index 02e8481a2e8..2004d757c44 100644 --- a/jena-arq/src/main/java/org/apache/jena/riot/WebContent.java +++ b/jena-arq/src/main/java/org/apache/jena/riot/WebContent.java @@ -123,7 +123,7 @@ public class WebContent { public static final ContentType ctResultsJSON = ContentType.create(contentTypeResultsJSON); public static final String contentTypeJSON = "application/json"; - public static final ContentType ctJSON = ContentType.create(contentTypeJSON); + public static final ContentType ctJSON = ContentType.create(contentTypeJSON); // Unofficial public static final String contentTypeResultsProtobuf = "application/sparql-results+protobuf" ; @@ -134,25 +134,34 @@ public class WebContent { public static final ContentType ctResultsThrift = ContentType.create(contentTypeResultsThrift) ; public static final String contentTypeSPARQLQuery = "application/sparql-query"; - public static final ContentType ctSPARQLQuery = ContentType.create(contentTypeSPARQLQuery); + public static final ContentType ctSPARQLQuery = ContentType.create(contentTypeSPARQLQuery); public static final String contentTypeSPARQLUpdate = "application/sparql-update"; - public static final ContentType ctSPARQLUpdate = ContentType.create(contentTypeSPARQLUpdate); + public static final ContentType ctSPARQLUpdate = ContentType.create(contentTypeSPARQLUpdate); public static final String contentTypeHTMLForm = "application/x-www-form-urlencoded"; - public static final ContentType ctHTMLForm = ContentType.create(contentTypeHTMLForm); + public static final ContentType ctHTMLForm = ContentType.create(contentTypeHTMLForm); public static final String contentTypeHTML = "text/html"; public static final ContentType ctTextHTML = ContentType.create(contentTypeHTML); public static final String contentTypeTextCSV = "text/csv"; - public static final ContentType ctTextCSV = ContentType.create(contentTypeTextCSV); + public static final ContentType ctTextCSV = ContentType.create(contentTypeTextCSV); public static final String contentTypeTextTSV = "text/tab-separated-values"; - public static final ContentType ctTextTSV = ContentType.create(contentTypeTextTSV); + public static final ContentType ctTextTSV = ContentType.create(contentTypeTextTSV); + // Unofficial public static final String contentTypeSSE = "text/sse"; - public static final ContentType ctSSE = ContentType.create(contentTypeSSE); + public static final ContentType ctSSE = ContentType.create(contentTypeSSE); + + // Unofficial + public static final String contentTypePatch = "application/rdf-patch"; + public static final ContentType ctPatch = ContentType.create(contentTypePatch); + + // Unofficial + public static final String contentTypePatchThrift = "application/rdf-patch+thrift"; + public static final ContentType ctPatchThrift = ContentType.create(contentTypePatchThrift); public static final String charsetUTF8 = "utf-8"; public static final String charsetASCII = "ascii"; diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/PatchTxn.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/PatchTxn.java new file mode 100644 index 00000000000..6c1761513b3 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/PatchTxn.java @@ -0,0 +1,48 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + + +public enum PatchTxn implements org.apache.thrift.TEnum { + TX(0), + TC(1), + TA(2), + Segment(3); + + private final int value; + + private PatchTxn(int value) { + this.value = value; + } + + /** + * Get the integer value of this enum value, as defined in the Thrift IDL. + */ + @Override public int getValue() { + return value; + } + + /** + * Find a the enum type by its integer value, as defined in the Thrift IDL. + * @return null if the value is not found. + */ + @org.apache.thrift.annotation.Nullable + public static PatchTxn findByValue(int value) { + switch (value) { + case 0: + return TX; + case 1: + return TC; + case 2: + return TA; + case 3: + return Segment; + default: + return null; + } + } +} diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Data_Add.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Data_Add.java new file mode 100644 index 00000000000..9c9e878c137 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Data_Add.java @@ -0,0 +1,688 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + +@SuppressWarnings("all") +public class Patch_Data_Add implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Patch_Data_Add"); + + private static final org.apache.thrift.protocol.TField S_FIELD_DESC = new org.apache.thrift.protocol.TField("s", org.apache.thrift.protocol.TType.STRUCT, (short)1); + private static final org.apache.thrift.protocol.TField P_FIELD_DESC = new org.apache.thrift.protocol.TField("p", org.apache.thrift.protocol.TType.STRUCT, (short)2); + private static final org.apache.thrift.protocol.TField O_FIELD_DESC = new org.apache.thrift.protocol.TField("o", org.apache.thrift.protocol.TType.STRUCT, (short)3); + private static final org.apache.thrift.protocol.TField G_FIELD_DESC = new org.apache.thrift.protocol.TField("g", org.apache.thrift.protocol.TType.STRUCT, (short)4); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new Patch_Data_AddStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new Patch_Data_AddTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable RDF_Term s; // required + public @org.apache.thrift.annotation.Nullable RDF_Term p; // required + public @org.apache.thrift.annotation.Nullable RDF_Term o; // required + public @org.apache.thrift.annotation.Nullable RDF_Term g; // optional + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + S((short)1, "s"), + P((short)2, "p"), + O((short)3, "o"), + G((short)4, "g"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // S + return S; + case 2: // P + return P; + case 3: // O + return O; + case 4: // G + return G; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + private static final _Fields optionals[] = {_Fields.G}; + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.S, new org.apache.thrift.meta_data.FieldMetaData("s", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.P, new org.apache.thrift.meta_data.FieldMetaData("p", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.O, new org.apache.thrift.meta_data.FieldMetaData("o", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.G, new org.apache.thrift.meta_data.FieldMetaData("g", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Patch_Data_Add.class, metaDataMap); + } + + public Patch_Data_Add() { + } + + public Patch_Data_Add( + RDF_Term s, + RDF_Term p, + RDF_Term o) + { + this(); + this.s = s; + this.p = p; + this.o = o; + } + + /** + * Performs a deep copy on other. + */ + public Patch_Data_Add(Patch_Data_Add other) { + if (other.isSetS()) { + this.s = new RDF_Term(other.s); + } + if (other.isSetP()) { + this.p = new RDF_Term(other.p); + } + if (other.isSetO()) { + this.o = new RDF_Term(other.o); + } + if (other.isSetG()) { + this.g = new RDF_Term(other.g); + } + } + + public Patch_Data_Add deepCopy() { + return new Patch_Data_Add(this); + } + + @Override + public void clear() { + this.s = null; + this.p = null; + this.o = null; + this.g = null; + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getS() { + return this.s; + } + + public Patch_Data_Add setS(@org.apache.thrift.annotation.Nullable RDF_Term s) { + this.s = s; + return this; + } + + public void unsetS() { + this.s = null; + } + + /** Returns true if field s is set (has been assigned a value) and false otherwise */ + public boolean isSetS() { + return this.s != null; + } + + public void setSIsSet(boolean value) { + if (!value) { + this.s = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getP() { + return this.p; + } + + public Patch_Data_Add setP(@org.apache.thrift.annotation.Nullable RDF_Term p) { + this.p = p; + return this; + } + + public void unsetP() { + this.p = null; + } + + /** Returns true if field p is set (has been assigned a value) and false otherwise */ + public boolean isSetP() { + return this.p != null; + } + + public void setPIsSet(boolean value) { + if (!value) { + this.p = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getO() { + return this.o; + } + + public Patch_Data_Add setO(@org.apache.thrift.annotation.Nullable RDF_Term o) { + this.o = o; + return this; + } + + public void unsetO() { + this.o = null; + } + + /** Returns true if field o is set (has been assigned a value) and false otherwise */ + public boolean isSetO() { + return this.o != null; + } + + public void setOIsSet(boolean value) { + if (!value) { + this.o = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getG() { + return this.g; + } + + public Patch_Data_Add setG(@org.apache.thrift.annotation.Nullable RDF_Term g) { + this.g = g; + return this; + } + + public void unsetG() { + this.g = null; + } + + /** Returns true if field g is set (has been assigned a value) and false otherwise */ + public boolean isSetG() { + return this.g != null; + } + + public void setGIsSet(boolean value) { + if (!value) { + this.g = null; + } + } + + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case S: + if (value == null) { + unsetS(); + } else { + setS((RDF_Term)value); + } + break; + + case P: + if (value == null) { + unsetP(); + } else { + setP((RDF_Term)value); + } + break; + + case O: + if (value == null) { + unsetO(); + } else { + setO((RDF_Term)value); + } + break; + + case G: + if (value == null) { + unsetG(); + } else { + setG((RDF_Term)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case S: + return getS(); + + case P: + return getP(); + + case O: + return getO(); + + case G: + return getG(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case S: + return isSetS(); + case P: + return isSetP(); + case O: + return isSetO(); + case G: + return isSetG(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof Patch_Data_Add) + return this.equals((Patch_Data_Add)that); + return false; + } + + public boolean equals(Patch_Data_Add that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_s = true && this.isSetS(); + boolean that_present_s = true && that.isSetS(); + if (this_present_s || that_present_s) { + if (!(this_present_s && that_present_s)) + return false; + if (!this.s.equals(that.s)) + return false; + } + + boolean this_present_p = true && this.isSetP(); + boolean that_present_p = true && that.isSetP(); + if (this_present_p || that_present_p) { + if (!(this_present_p && that_present_p)) + return false; + if (!this.p.equals(that.p)) + return false; + } + + boolean this_present_o = true && this.isSetO(); + boolean that_present_o = true && that.isSetO(); + if (this_present_o || that_present_o) { + if (!(this_present_o && that_present_o)) + return false; + if (!this.o.equals(that.o)) + return false; + } + + boolean this_present_g = true && this.isSetG(); + boolean that_present_g = true && that.isSetG(); + if (this_present_g || that_present_g) { + if (!(this_present_g && that_present_g)) + return false; + if (!this.g.equals(that.g)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetS()) ? 131071 : 524287); + if (isSetS()) + hashCode = hashCode * 8191 + s.hashCode(); + + hashCode = hashCode * 8191 + ((isSetP()) ? 131071 : 524287); + if (isSetP()) + hashCode = hashCode * 8191 + p.hashCode(); + + hashCode = hashCode * 8191 + ((isSetO()) ? 131071 : 524287); + if (isSetO()) + hashCode = hashCode * 8191 + o.hashCode(); + + hashCode = hashCode * 8191 + ((isSetG()) ? 131071 : 524287); + if (isSetG()) + hashCode = hashCode * 8191 + g.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(Patch_Data_Add other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetS(), other.isSetS()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetS()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.s, other.s); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetP(), other.isSetP()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetP()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.p, other.p); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetO(), other.isSetO()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetO()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.o, other.o); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetG(), other.isSetG()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetG()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.g, other.g); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("Patch_Data_Add("); + boolean first = true; + + sb.append("s:"); + if (this.s == null) { + sb.append("null"); + } else { + sb.append(this.s); + } + first = false; + if (!first) sb.append(", "); + sb.append("p:"); + if (this.p == null) { + sb.append("null"); + } else { + sb.append(this.p); + } + first = false; + if (!first) sb.append(", "); + sb.append("o:"); + if (this.o == null) { + sb.append("null"); + } else { + sb.append(this.o); + } + first = false; + if (isSetG()) { + if (!first) sb.append(", "); + sb.append("g:"); + if (this.g == null) { + sb.append("null"); + } else { + sb.append(this.g); + } + first = false; + } + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + if (s == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 's' was not present! Struct: " + toString()); + } + if (p == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'p' was not present! Struct: " + toString()); + } + if (o == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'o' was not present! Struct: " + toString()); + } + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class Patch_Data_AddStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Data_AddStandardScheme getScheme() { + return new Patch_Data_AddStandardScheme(); + } + } + + private static class Patch_Data_AddStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + public void read(org.apache.thrift.protocol.TProtocol iprot, Patch_Data_Add struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // S + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.s = new RDF_Term(); + struct.s.read(iprot); + struct.setSIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // P + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.p = new RDF_Term(); + struct.p.read(iprot); + struct.setPIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 3: // O + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.o = new RDF_Term(); + struct.o.read(iprot); + struct.setOIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 4: // G + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.g = new RDF_Term(); + struct.g.read(iprot); + struct.setGIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot, Patch_Data_Add struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.s != null) { + oprot.writeFieldBegin(S_FIELD_DESC); + struct.s.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.p != null) { + oprot.writeFieldBegin(P_FIELD_DESC); + struct.p.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.o != null) { + oprot.writeFieldBegin(O_FIELD_DESC); + struct.o.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.g != null) { + if (struct.isSetG()) { + oprot.writeFieldBegin(G_FIELD_DESC); + struct.g.write(oprot); + oprot.writeFieldEnd(); + } + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class Patch_Data_AddTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Data_AddTupleScheme getScheme() { + return new Patch_Data_AddTupleScheme(); + } + } + + private static class Patch_Data_AddTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, Patch_Data_Add struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.s.write(oprot); + struct.p.write(oprot); + struct.o.write(oprot); + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetG()) { + optionals.set(0); + } + oprot.writeBitSet(optionals, 1); + if (struct.isSetG()) { + struct.g.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, Patch_Data_Add struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.s = new RDF_Term(); + struct.s.read(iprot); + struct.setSIsSet(true); + struct.p = new RDF_Term(); + struct.p.read(iprot); + struct.setPIsSet(true); + struct.o = new RDF_Term(); + struct.o.read(iprot); + struct.setOIsSet(true); + java.util.BitSet incoming = iprot.readBitSet(1); + if (incoming.get(0)) { + struct.g = new RDF_Term(); + struct.g.read(iprot); + struct.setGIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Data_Del.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Data_Del.java new file mode 100644 index 00000000000..8f079dda012 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Data_Del.java @@ -0,0 +1,688 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + +@SuppressWarnings("all") +public class Patch_Data_Del implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Patch_Data_Del"); + + private static final org.apache.thrift.protocol.TField S_FIELD_DESC = new org.apache.thrift.protocol.TField("s", org.apache.thrift.protocol.TType.STRUCT, (short)1); + private static final org.apache.thrift.protocol.TField P_FIELD_DESC = new org.apache.thrift.protocol.TField("p", org.apache.thrift.protocol.TType.STRUCT, (short)2); + private static final org.apache.thrift.protocol.TField O_FIELD_DESC = new org.apache.thrift.protocol.TField("o", org.apache.thrift.protocol.TType.STRUCT, (short)3); + private static final org.apache.thrift.protocol.TField G_FIELD_DESC = new org.apache.thrift.protocol.TField("g", org.apache.thrift.protocol.TType.STRUCT, (short)4); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new Patch_Data_DelStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new Patch_Data_DelTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable RDF_Term s; // required + public @org.apache.thrift.annotation.Nullable RDF_Term p; // required + public @org.apache.thrift.annotation.Nullable RDF_Term o; // required + public @org.apache.thrift.annotation.Nullable RDF_Term g; // optional + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + S((short)1, "s"), + P((short)2, "p"), + O((short)3, "o"), + G((short)4, "g"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // S + return S; + case 2: // P + return P; + case 3: // O + return O; + case 4: // G + return G; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + private static final _Fields optionals[] = {_Fields.G}; + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.S, new org.apache.thrift.meta_data.FieldMetaData("s", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.P, new org.apache.thrift.meta_data.FieldMetaData("p", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.O, new org.apache.thrift.meta_data.FieldMetaData("o", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.G, new org.apache.thrift.meta_data.FieldMetaData("g", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Patch_Data_Del.class, metaDataMap); + } + + public Patch_Data_Del() { + } + + public Patch_Data_Del( + RDF_Term s, + RDF_Term p, + RDF_Term o) + { + this(); + this.s = s; + this.p = p; + this.o = o; + } + + /** + * Performs a deep copy on other. + */ + public Patch_Data_Del(Patch_Data_Del other) { + if (other.isSetS()) { + this.s = new RDF_Term(other.s); + } + if (other.isSetP()) { + this.p = new RDF_Term(other.p); + } + if (other.isSetO()) { + this.o = new RDF_Term(other.o); + } + if (other.isSetG()) { + this.g = new RDF_Term(other.g); + } + } + + public Patch_Data_Del deepCopy() { + return new Patch_Data_Del(this); + } + + @Override + public void clear() { + this.s = null; + this.p = null; + this.o = null; + this.g = null; + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getS() { + return this.s; + } + + public Patch_Data_Del setS(@org.apache.thrift.annotation.Nullable RDF_Term s) { + this.s = s; + return this; + } + + public void unsetS() { + this.s = null; + } + + /** Returns true if field s is set (has been assigned a value) and false otherwise */ + public boolean isSetS() { + return this.s != null; + } + + public void setSIsSet(boolean value) { + if (!value) { + this.s = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getP() { + return this.p; + } + + public Patch_Data_Del setP(@org.apache.thrift.annotation.Nullable RDF_Term p) { + this.p = p; + return this; + } + + public void unsetP() { + this.p = null; + } + + /** Returns true if field p is set (has been assigned a value) and false otherwise */ + public boolean isSetP() { + return this.p != null; + } + + public void setPIsSet(boolean value) { + if (!value) { + this.p = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getO() { + return this.o; + } + + public Patch_Data_Del setO(@org.apache.thrift.annotation.Nullable RDF_Term o) { + this.o = o; + return this; + } + + public void unsetO() { + this.o = null; + } + + /** Returns true if field o is set (has been assigned a value) and false otherwise */ + public boolean isSetO() { + return this.o != null; + } + + public void setOIsSet(boolean value) { + if (!value) { + this.o = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getG() { + return this.g; + } + + public Patch_Data_Del setG(@org.apache.thrift.annotation.Nullable RDF_Term g) { + this.g = g; + return this; + } + + public void unsetG() { + this.g = null; + } + + /** Returns true if field g is set (has been assigned a value) and false otherwise */ + public boolean isSetG() { + return this.g != null; + } + + public void setGIsSet(boolean value) { + if (!value) { + this.g = null; + } + } + + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case S: + if (value == null) { + unsetS(); + } else { + setS((RDF_Term)value); + } + break; + + case P: + if (value == null) { + unsetP(); + } else { + setP((RDF_Term)value); + } + break; + + case O: + if (value == null) { + unsetO(); + } else { + setO((RDF_Term)value); + } + break; + + case G: + if (value == null) { + unsetG(); + } else { + setG((RDF_Term)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case S: + return getS(); + + case P: + return getP(); + + case O: + return getO(); + + case G: + return getG(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case S: + return isSetS(); + case P: + return isSetP(); + case O: + return isSetO(); + case G: + return isSetG(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof Patch_Data_Del) + return this.equals((Patch_Data_Del)that); + return false; + } + + public boolean equals(Patch_Data_Del that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_s = true && this.isSetS(); + boolean that_present_s = true && that.isSetS(); + if (this_present_s || that_present_s) { + if (!(this_present_s && that_present_s)) + return false; + if (!this.s.equals(that.s)) + return false; + } + + boolean this_present_p = true && this.isSetP(); + boolean that_present_p = true && that.isSetP(); + if (this_present_p || that_present_p) { + if (!(this_present_p && that_present_p)) + return false; + if (!this.p.equals(that.p)) + return false; + } + + boolean this_present_o = true && this.isSetO(); + boolean that_present_o = true && that.isSetO(); + if (this_present_o || that_present_o) { + if (!(this_present_o && that_present_o)) + return false; + if (!this.o.equals(that.o)) + return false; + } + + boolean this_present_g = true && this.isSetG(); + boolean that_present_g = true && that.isSetG(); + if (this_present_g || that_present_g) { + if (!(this_present_g && that_present_g)) + return false; + if (!this.g.equals(that.g)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetS()) ? 131071 : 524287); + if (isSetS()) + hashCode = hashCode * 8191 + s.hashCode(); + + hashCode = hashCode * 8191 + ((isSetP()) ? 131071 : 524287); + if (isSetP()) + hashCode = hashCode * 8191 + p.hashCode(); + + hashCode = hashCode * 8191 + ((isSetO()) ? 131071 : 524287); + if (isSetO()) + hashCode = hashCode * 8191 + o.hashCode(); + + hashCode = hashCode * 8191 + ((isSetG()) ? 131071 : 524287); + if (isSetG()) + hashCode = hashCode * 8191 + g.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(Patch_Data_Del other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetS(), other.isSetS()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetS()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.s, other.s); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetP(), other.isSetP()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetP()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.p, other.p); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetO(), other.isSetO()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetO()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.o, other.o); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetG(), other.isSetG()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetG()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.g, other.g); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("Patch_Data_Del("); + boolean first = true; + + sb.append("s:"); + if (this.s == null) { + sb.append("null"); + } else { + sb.append(this.s); + } + first = false; + if (!first) sb.append(", "); + sb.append("p:"); + if (this.p == null) { + sb.append("null"); + } else { + sb.append(this.p); + } + first = false; + if (!first) sb.append(", "); + sb.append("o:"); + if (this.o == null) { + sb.append("null"); + } else { + sb.append(this.o); + } + first = false; + if (isSetG()) { + if (!first) sb.append(", "); + sb.append("g:"); + if (this.g == null) { + sb.append("null"); + } else { + sb.append(this.g); + } + first = false; + } + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + if (s == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 's' was not present! Struct: " + toString()); + } + if (p == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'p' was not present! Struct: " + toString()); + } + if (o == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'o' was not present! Struct: " + toString()); + } + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class Patch_Data_DelStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Data_DelStandardScheme getScheme() { + return new Patch_Data_DelStandardScheme(); + } + } + + private static class Patch_Data_DelStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + public void read(org.apache.thrift.protocol.TProtocol iprot, Patch_Data_Del struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // S + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.s = new RDF_Term(); + struct.s.read(iprot); + struct.setSIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // P + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.p = new RDF_Term(); + struct.p.read(iprot); + struct.setPIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 3: // O + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.o = new RDF_Term(); + struct.o.read(iprot); + struct.setOIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 4: // G + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.g = new RDF_Term(); + struct.g.read(iprot); + struct.setGIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot, Patch_Data_Del struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.s != null) { + oprot.writeFieldBegin(S_FIELD_DESC); + struct.s.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.p != null) { + oprot.writeFieldBegin(P_FIELD_DESC); + struct.p.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.o != null) { + oprot.writeFieldBegin(O_FIELD_DESC); + struct.o.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.g != null) { + if (struct.isSetG()) { + oprot.writeFieldBegin(G_FIELD_DESC); + struct.g.write(oprot); + oprot.writeFieldEnd(); + } + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class Patch_Data_DelTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Data_DelTupleScheme getScheme() { + return new Patch_Data_DelTupleScheme(); + } + } + + private static class Patch_Data_DelTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, Patch_Data_Del struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.s.write(oprot); + struct.p.write(oprot); + struct.o.write(oprot); + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetG()) { + optionals.set(0); + } + oprot.writeBitSet(optionals, 1); + if (struct.isSetG()) { + struct.g.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, Patch_Data_Del struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.s = new RDF_Term(); + struct.s.read(iprot); + struct.setSIsSet(true); + struct.p = new RDF_Term(); + struct.p.read(iprot); + struct.setPIsSet(true); + struct.o = new RDF_Term(); + struct.o.read(iprot); + struct.setOIsSet(true); + java.util.BitSet incoming = iprot.readBitSet(1); + if (incoming.get(0)) { + struct.g = new RDF_Term(); + struct.g.read(iprot); + struct.setGIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Header.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Header.java new file mode 100644 index 00000000000..3c0eed3c110 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Header.java @@ -0,0 +1,470 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + +@SuppressWarnings("all") +public class Patch_Header implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Patch_Header"); + + private static final org.apache.thrift.protocol.TField NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("name", org.apache.thrift.protocol.TType.STRING, (short)1); + private static final org.apache.thrift.protocol.TField VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("value", org.apache.thrift.protocol.TType.STRUCT, (short)2); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new Patch_HeaderStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new Patch_HeaderTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable java.lang.String name; // required + public @org.apache.thrift.annotation.Nullable RDF_Term value; // required + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + NAME((short)1, "name"), + VALUE((short)2, "value"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // NAME + return NAME; + case 2: // VALUE + return VALUE; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.NAME, new org.apache.thrift.meta_data.FieldMetaData("name", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + tmpMap.put(_Fields.VALUE, new org.apache.thrift.meta_data.FieldMetaData("value", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Patch_Header.class, metaDataMap); + } + + public Patch_Header() { + } + + public Patch_Header( + java.lang.String name, + RDF_Term value) + { + this(); + this.name = name; + this.value = value; + } + + /** + * Performs a deep copy on other. + */ + public Patch_Header(Patch_Header other) { + if (other.isSetName()) { + this.name = other.name; + } + if (other.isSetValue()) { + this.value = new RDF_Term(other.value); + } + } + + public Patch_Header deepCopy() { + return new Patch_Header(this); + } + + @Override + public void clear() { + this.name = null; + this.value = null; + } + + @org.apache.thrift.annotation.Nullable + public java.lang.String getName() { + return this.name; + } + + public Patch_Header setName(@org.apache.thrift.annotation.Nullable java.lang.String name) { + this.name = name; + return this; + } + + public void unsetName() { + this.name = null; + } + + /** Returns true if field name is set (has been assigned a value) and false otherwise */ + public boolean isSetName() { + return this.name != null; + } + + public void setNameIsSet(boolean value) { + if (!value) { + this.name = null; + } + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getValue() { + return this.value; + } + + public Patch_Header setValue(@org.apache.thrift.annotation.Nullable RDF_Term value) { + this.value = value; + return this; + } + + public void unsetValue() { + this.value = null; + } + + /** Returns true if field value is set (has been assigned a value) and false otherwise */ + public boolean isSetValue() { + return this.value != null; + } + + public void setValueIsSet(boolean value) { + if (!value) { + this.value = null; + } + } + + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case NAME: + if (value == null) { + unsetName(); + } else { + setName((java.lang.String)value); + } + break; + + case VALUE: + if (value == null) { + unsetValue(); + } else { + setValue((RDF_Term)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case NAME: + return getName(); + + case VALUE: + return getValue(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case NAME: + return isSetName(); + case VALUE: + return isSetValue(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof Patch_Header) + return this.equals((Patch_Header)that); + return false; + } + + public boolean equals(Patch_Header that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_name = true && this.isSetName(); + boolean that_present_name = true && that.isSetName(); + if (this_present_name || that_present_name) { + if (!(this_present_name && that_present_name)) + return false; + if (!this.name.equals(that.name)) + return false; + } + + boolean this_present_value = true && this.isSetValue(); + boolean that_present_value = true && that.isSetValue(); + if (this_present_value || that_present_value) { + if (!(this_present_value && that_present_value)) + return false; + if (!this.value.equals(that.value)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetName()) ? 131071 : 524287); + if (isSetName()) + hashCode = hashCode * 8191 + name.hashCode(); + + hashCode = hashCode * 8191 + ((isSetValue()) ? 131071 : 524287); + if (isSetValue()) + hashCode = hashCode * 8191 + value.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(Patch_Header other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetName(), other.isSetName()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetName()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.name, other.name); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetValue(), other.isSetValue()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetValue()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.value, other.value); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("Patch_Header("); + boolean first = true; + + sb.append("name:"); + if (this.name == null) { + sb.append("null"); + } else { + sb.append(this.name); + } + first = false; + if (!first) sb.append(", "); + sb.append("value:"); + if (this.value == null) { + sb.append("null"); + } else { + sb.append(this.value); + } + first = false; + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + if (name == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'name' was not present! Struct: " + toString()); + } + if (value == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'value' was not present! Struct: " + toString()); + } + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class Patch_HeaderStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_HeaderStandardScheme getScheme() { + return new Patch_HeaderStandardScheme(); + } + } + + private static class Patch_HeaderStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + public void read(org.apache.thrift.protocol.TProtocol iprot, Patch_Header struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // NAME + if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { + struct.name = iprot.readString(); + struct.setNameIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // VALUE + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.value = new RDF_Term(); + struct.value.read(iprot); + struct.setValueIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot, Patch_Header struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.name != null) { + oprot.writeFieldBegin(NAME_FIELD_DESC); + oprot.writeString(struct.name); + oprot.writeFieldEnd(); + } + if (struct.value != null) { + oprot.writeFieldBegin(VALUE_FIELD_DESC); + struct.value.write(oprot); + oprot.writeFieldEnd(); + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class Patch_HeaderTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_HeaderTupleScheme getScheme() { + return new Patch_HeaderTupleScheme(); + } + } + + private static class Patch_HeaderTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, Patch_Header struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + oprot.writeString(struct.name); + struct.value.write(oprot); + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, Patch_Header struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.name = iprot.readString(); + struct.setNameIsSet(true); + struct.value = new RDF_Term(); + struct.value.read(iprot); + struct.setValueIsSet(true); + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Prefix_Add.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Prefix_Add.java new file mode 100644 index 00000000000..f4075442723 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Prefix_Add.java @@ -0,0 +1,581 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + +@SuppressWarnings("all") +public class Patch_Prefix_Add implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Patch_Prefix_Add"); + + private static final org.apache.thrift.protocol.TField GRAPH_NODE_FIELD_DESC = new org.apache.thrift.protocol.TField("graphNode", org.apache.thrift.protocol.TType.STRUCT, (short)1); + private static final org.apache.thrift.protocol.TField PREFIX_FIELD_DESC = new org.apache.thrift.protocol.TField("prefix", org.apache.thrift.protocol.TType.STRING, (short)2); + private static final org.apache.thrift.protocol.TField IRI_STR_FIELD_DESC = new org.apache.thrift.protocol.TField("iriStr", org.apache.thrift.protocol.TType.STRING, (short)3); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new Patch_Prefix_AddStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new Patch_Prefix_AddTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable RDF_Term graphNode; // optional + public @org.apache.thrift.annotation.Nullable java.lang.String prefix; // required + public @org.apache.thrift.annotation.Nullable java.lang.String iriStr; // required + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + GRAPH_NODE((short)1, "graphNode"), + PREFIX((short)2, "prefix"), + IRI_STR((short)3, "iriStr"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // GRAPH_NODE + return GRAPH_NODE; + case 2: // PREFIX + return PREFIX; + case 3: // IRI_STR + return IRI_STR; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + private static final _Fields optionals[] = {_Fields.GRAPH_NODE}; + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.GRAPH_NODE, new org.apache.thrift.meta_data.FieldMetaData("graphNode", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.PREFIX, new org.apache.thrift.meta_data.FieldMetaData("prefix", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + tmpMap.put(_Fields.IRI_STR, new org.apache.thrift.meta_data.FieldMetaData("iriStr", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Patch_Prefix_Add.class, metaDataMap); + } + + public Patch_Prefix_Add() { + } + + public Patch_Prefix_Add( + java.lang.String prefix, + java.lang.String iriStr) + { + this(); + this.prefix = prefix; + this.iriStr = iriStr; + } + + /** + * Performs a deep copy on other. + */ + public Patch_Prefix_Add(Patch_Prefix_Add other) { + if (other.isSetGraphNode()) { + this.graphNode = new RDF_Term(other.graphNode); + } + if (other.isSetPrefix()) { + this.prefix = other.prefix; + } + if (other.isSetIriStr()) { + this.iriStr = other.iriStr; + } + } + + public Patch_Prefix_Add deepCopy() { + return new Patch_Prefix_Add(this); + } + + @Override + public void clear() { + this.graphNode = null; + this.prefix = null; + this.iriStr = null; + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getGraphNode() { + return this.graphNode; + } + + public Patch_Prefix_Add setGraphNode(@org.apache.thrift.annotation.Nullable RDF_Term graphNode) { + this.graphNode = graphNode; + return this; + } + + public void unsetGraphNode() { + this.graphNode = null; + } + + /** Returns true if field graphNode is set (has been assigned a value) and false otherwise */ + public boolean isSetGraphNode() { + return this.graphNode != null; + } + + public void setGraphNodeIsSet(boolean value) { + if (!value) { + this.graphNode = null; + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.String getPrefix() { + return this.prefix; + } + + public Patch_Prefix_Add setPrefix(@org.apache.thrift.annotation.Nullable java.lang.String prefix) { + this.prefix = prefix; + return this; + } + + public void unsetPrefix() { + this.prefix = null; + } + + /** Returns true if field prefix is set (has been assigned a value) and false otherwise */ + public boolean isSetPrefix() { + return this.prefix != null; + } + + public void setPrefixIsSet(boolean value) { + if (!value) { + this.prefix = null; + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.String getIriStr() { + return this.iriStr; + } + + public Patch_Prefix_Add setIriStr(@org.apache.thrift.annotation.Nullable java.lang.String iriStr) { + this.iriStr = iriStr; + return this; + } + + public void unsetIriStr() { + this.iriStr = null; + } + + /** Returns true if field iriStr is set (has been assigned a value) and false otherwise */ + public boolean isSetIriStr() { + return this.iriStr != null; + } + + public void setIriStrIsSet(boolean value) { + if (!value) { + this.iriStr = null; + } + } + + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case GRAPH_NODE: + if (value == null) { + unsetGraphNode(); + } else { + setGraphNode((RDF_Term)value); + } + break; + + case PREFIX: + if (value == null) { + unsetPrefix(); + } else { + setPrefix((java.lang.String)value); + } + break; + + case IRI_STR: + if (value == null) { + unsetIriStr(); + } else { + setIriStr((java.lang.String)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case GRAPH_NODE: + return getGraphNode(); + + case PREFIX: + return getPrefix(); + + case IRI_STR: + return getIriStr(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case GRAPH_NODE: + return isSetGraphNode(); + case PREFIX: + return isSetPrefix(); + case IRI_STR: + return isSetIriStr(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof Patch_Prefix_Add) + return this.equals((Patch_Prefix_Add)that); + return false; + } + + public boolean equals(Patch_Prefix_Add that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_graphNode = true && this.isSetGraphNode(); + boolean that_present_graphNode = true && that.isSetGraphNode(); + if (this_present_graphNode || that_present_graphNode) { + if (!(this_present_graphNode && that_present_graphNode)) + return false; + if (!this.graphNode.equals(that.graphNode)) + return false; + } + + boolean this_present_prefix = true && this.isSetPrefix(); + boolean that_present_prefix = true && that.isSetPrefix(); + if (this_present_prefix || that_present_prefix) { + if (!(this_present_prefix && that_present_prefix)) + return false; + if (!this.prefix.equals(that.prefix)) + return false; + } + + boolean this_present_iriStr = true && this.isSetIriStr(); + boolean that_present_iriStr = true && that.isSetIriStr(); + if (this_present_iriStr || that_present_iriStr) { + if (!(this_present_iriStr && that_present_iriStr)) + return false; + if (!this.iriStr.equals(that.iriStr)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetGraphNode()) ? 131071 : 524287); + if (isSetGraphNode()) + hashCode = hashCode * 8191 + graphNode.hashCode(); + + hashCode = hashCode * 8191 + ((isSetPrefix()) ? 131071 : 524287); + if (isSetPrefix()) + hashCode = hashCode * 8191 + prefix.hashCode(); + + hashCode = hashCode * 8191 + ((isSetIriStr()) ? 131071 : 524287); + if (isSetIriStr()) + hashCode = hashCode * 8191 + iriStr.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(Patch_Prefix_Add other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetGraphNode(), other.isSetGraphNode()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetGraphNode()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.graphNode, other.graphNode); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetPrefix(), other.isSetPrefix()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetPrefix()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.prefix, other.prefix); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetIriStr(), other.isSetIriStr()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetIriStr()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.iriStr, other.iriStr); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("Patch_Prefix_Add("); + boolean first = true; + + if (isSetGraphNode()) { + sb.append("graphNode:"); + if (this.graphNode == null) { + sb.append("null"); + } else { + sb.append(this.graphNode); + } + first = false; + } + if (!first) sb.append(", "); + sb.append("prefix:"); + if (this.prefix == null) { + sb.append("null"); + } else { + sb.append(this.prefix); + } + first = false; + if (!first) sb.append(", "); + sb.append("iriStr:"); + if (this.iriStr == null) { + sb.append("null"); + } else { + sb.append(this.iriStr); + } + first = false; + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + if (prefix == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'prefix' was not present! Struct: " + toString()); + } + if (iriStr == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'iriStr' was not present! Struct: " + toString()); + } + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class Patch_Prefix_AddStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Prefix_AddStandardScheme getScheme() { + return new Patch_Prefix_AddStandardScheme(); + } + } + + private static class Patch_Prefix_AddStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + public void read(org.apache.thrift.protocol.TProtocol iprot, Patch_Prefix_Add struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // GRAPH_NODE + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.graphNode = new RDF_Term(); + struct.graphNode.read(iprot); + struct.setGraphNodeIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // PREFIX + if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { + struct.prefix = iprot.readString(); + struct.setPrefixIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 3: // IRI_STR + if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { + struct.iriStr = iprot.readString(); + struct.setIriStrIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot, Patch_Prefix_Add struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.graphNode != null) { + if (struct.isSetGraphNode()) { + oprot.writeFieldBegin(GRAPH_NODE_FIELD_DESC); + struct.graphNode.write(oprot); + oprot.writeFieldEnd(); + } + } + if (struct.prefix != null) { + oprot.writeFieldBegin(PREFIX_FIELD_DESC); + oprot.writeString(struct.prefix); + oprot.writeFieldEnd(); + } + if (struct.iriStr != null) { + oprot.writeFieldBegin(IRI_STR_FIELD_DESC); + oprot.writeString(struct.iriStr); + oprot.writeFieldEnd(); + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class Patch_Prefix_AddTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Prefix_AddTupleScheme getScheme() { + return new Patch_Prefix_AddTupleScheme(); + } + } + + private static class Patch_Prefix_AddTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, Patch_Prefix_Add struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + oprot.writeString(struct.prefix); + oprot.writeString(struct.iriStr); + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetGraphNode()) { + optionals.set(0); + } + oprot.writeBitSet(optionals, 1); + if (struct.isSetGraphNode()) { + struct.graphNode.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, Patch_Prefix_Add struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.prefix = iprot.readString(); + struct.setPrefixIsSet(true); + struct.iriStr = iprot.readString(); + struct.setIriStrIsSet(true); + java.util.BitSet incoming = iprot.readBitSet(1); + if (incoming.get(0)) { + struct.graphNode = new RDF_Term(); + struct.graphNode.read(iprot); + struct.setGraphNodeIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Prefix_Del.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Prefix_Del.java new file mode 100644 index 00000000000..67a6221e9cb --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/Patch_Prefix_Del.java @@ -0,0 +1,480 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + +@SuppressWarnings("all") +public class Patch_Prefix_Del implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Patch_Prefix_Del"); + + private static final org.apache.thrift.protocol.TField GRAPH_NODE_FIELD_DESC = new org.apache.thrift.protocol.TField("graphNode", org.apache.thrift.protocol.TType.STRUCT, (short)1); + private static final org.apache.thrift.protocol.TField PREFIX_FIELD_DESC = new org.apache.thrift.protocol.TField("prefix", org.apache.thrift.protocol.TType.STRING, (short)2); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new Patch_Prefix_DelStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new Patch_Prefix_DelTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable RDF_Term graphNode; // optional + public @org.apache.thrift.annotation.Nullable java.lang.String prefix; // required + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + GRAPH_NODE((short)1, "graphNode"), + PREFIX((short)2, "prefix"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // GRAPH_NODE + return GRAPH_NODE; + case 2: // PREFIX + return PREFIX; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + private static final _Fields optionals[] = {_Fields.GRAPH_NODE}; + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.GRAPH_NODE, new org.apache.thrift.meta_data.FieldMetaData("graphNode", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RDF_Term.class))); + tmpMap.put(_Fields.PREFIX, new org.apache.thrift.meta_data.FieldMetaData("prefix", org.apache.thrift.TFieldRequirementType.REQUIRED, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Patch_Prefix_Del.class, metaDataMap); + } + + public Patch_Prefix_Del() { + } + + public Patch_Prefix_Del( + java.lang.String prefix) + { + this(); + this.prefix = prefix; + } + + /** + * Performs a deep copy on other. + */ + public Patch_Prefix_Del(Patch_Prefix_Del other) { + if (other.isSetGraphNode()) { + this.graphNode = new RDF_Term(other.graphNode); + } + if (other.isSetPrefix()) { + this.prefix = other.prefix; + } + } + + public Patch_Prefix_Del deepCopy() { + return new Patch_Prefix_Del(this); + } + + @Override + public void clear() { + this.graphNode = null; + this.prefix = null; + } + + @org.apache.thrift.annotation.Nullable + public RDF_Term getGraphNode() { + return this.graphNode; + } + + public Patch_Prefix_Del setGraphNode(@org.apache.thrift.annotation.Nullable RDF_Term graphNode) { + this.graphNode = graphNode; + return this; + } + + public void unsetGraphNode() { + this.graphNode = null; + } + + /** Returns true if field graphNode is set (has been assigned a value) and false otherwise */ + public boolean isSetGraphNode() { + return this.graphNode != null; + } + + public void setGraphNodeIsSet(boolean value) { + if (!value) { + this.graphNode = null; + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.String getPrefix() { + return this.prefix; + } + + public Patch_Prefix_Del setPrefix(@org.apache.thrift.annotation.Nullable java.lang.String prefix) { + this.prefix = prefix; + return this; + } + + public void unsetPrefix() { + this.prefix = null; + } + + /** Returns true if field prefix is set (has been assigned a value) and false otherwise */ + public boolean isSetPrefix() { + return this.prefix != null; + } + + public void setPrefixIsSet(boolean value) { + if (!value) { + this.prefix = null; + } + } + + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case GRAPH_NODE: + if (value == null) { + unsetGraphNode(); + } else { + setGraphNode((RDF_Term)value); + } + break; + + case PREFIX: + if (value == null) { + unsetPrefix(); + } else { + setPrefix((java.lang.String)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case GRAPH_NODE: + return getGraphNode(); + + case PREFIX: + return getPrefix(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case GRAPH_NODE: + return isSetGraphNode(); + case PREFIX: + return isSetPrefix(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof Patch_Prefix_Del) + return this.equals((Patch_Prefix_Del)that); + return false; + } + + public boolean equals(Patch_Prefix_Del that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_graphNode = true && this.isSetGraphNode(); + boolean that_present_graphNode = true && that.isSetGraphNode(); + if (this_present_graphNode || that_present_graphNode) { + if (!(this_present_graphNode && that_present_graphNode)) + return false; + if (!this.graphNode.equals(that.graphNode)) + return false; + } + + boolean this_present_prefix = true && this.isSetPrefix(); + boolean that_present_prefix = true && that.isSetPrefix(); + if (this_present_prefix || that_present_prefix) { + if (!(this_present_prefix && that_present_prefix)) + return false; + if (!this.prefix.equals(that.prefix)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetGraphNode()) ? 131071 : 524287); + if (isSetGraphNode()) + hashCode = hashCode * 8191 + graphNode.hashCode(); + + hashCode = hashCode * 8191 + ((isSetPrefix()) ? 131071 : 524287); + if (isSetPrefix()) + hashCode = hashCode * 8191 + prefix.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(Patch_Prefix_Del other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetGraphNode(), other.isSetGraphNode()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetGraphNode()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.graphNode, other.graphNode); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetPrefix(), other.isSetPrefix()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetPrefix()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.prefix, other.prefix); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("Patch_Prefix_Del("); + boolean first = true; + + if (isSetGraphNode()) { + sb.append("graphNode:"); + if (this.graphNode == null) { + sb.append("null"); + } else { + sb.append(this.graphNode); + } + first = false; + } + if (!first) sb.append(", "); + sb.append("prefix:"); + if (this.prefix == null) { + sb.append("null"); + } else { + sb.append(this.prefix); + } + first = false; + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + if (prefix == null) { + throw new org.apache.thrift.protocol.TProtocolException("Required field 'prefix' was not present! Struct: " + toString()); + } + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class Patch_Prefix_DelStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Prefix_DelStandardScheme getScheme() { + return new Patch_Prefix_DelStandardScheme(); + } + } + + private static class Patch_Prefix_DelStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + public void read(org.apache.thrift.protocol.TProtocol iprot, Patch_Prefix_Del struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // GRAPH_NODE + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.graphNode = new RDF_Term(); + struct.graphNode.read(iprot); + struct.setGraphNodeIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // PREFIX + if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { + struct.prefix = iprot.readString(); + struct.setPrefixIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot, Patch_Prefix_Del struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.graphNode != null) { + if (struct.isSetGraphNode()) { + oprot.writeFieldBegin(GRAPH_NODE_FIELD_DESC); + struct.graphNode.write(oprot); + oprot.writeFieldEnd(); + } + } + if (struct.prefix != null) { + oprot.writeFieldBegin(PREFIX_FIELD_DESC); + oprot.writeString(struct.prefix); + oprot.writeFieldEnd(); + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class Patch_Prefix_DelTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public Patch_Prefix_DelTupleScheme getScheme() { + return new Patch_Prefix_DelTupleScheme(); + } + } + + private static class Patch_Prefix_DelTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, Patch_Prefix_Del struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + oprot.writeString(struct.prefix); + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetGraphNode()) { + optionals.set(0); + } + oprot.writeBitSet(optionals, 1); + if (struct.isSetGraphNode()) { + struct.graphNode.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, Patch_Prefix_Del struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + struct.prefix = iprot.readString(); + struct.setPrefixIsSet(true); + java.util.BitSet incoming = iprot.readBitSet(1); + if (incoming.get(0)) { + struct.graphNode = new RDF_Term(); + struct.graphNode.read(iprot); + struct.setGraphNodeIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/RDF_Patch_Row.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/RDF_Patch_Row.java new file mode 100644 index 00000000000..9eb998196fc --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/wire/RDF_Patch_Row.java @@ -0,0 +1,594 @@ +/** + * Autogenerated by Thrift Compiler (0.16.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.jena.riot.thrift.wire; + +@SuppressWarnings("all") +public class RDF_Patch_Row extends org.apache.thrift.TUnion { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("RDF_Patch_Row"); + private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1); + private static final org.apache.thrift.protocol.TField DATA_ADD_FIELD_DESC = new org.apache.thrift.protocol.TField("dataAdd", org.apache.thrift.protocol.TType.STRUCT, (short)2); + private static final org.apache.thrift.protocol.TField DATA_DEL_FIELD_DESC = new org.apache.thrift.protocol.TField("dataDel", org.apache.thrift.protocol.TType.STRUCT, (short)3); + private static final org.apache.thrift.protocol.TField PREFIX_ADD_FIELD_DESC = new org.apache.thrift.protocol.TField("prefixAdd", org.apache.thrift.protocol.TType.STRUCT, (short)4); + private static final org.apache.thrift.protocol.TField PREFIX_DEL_FIELD_DESC = new org.apache.thrift.protocol.TField("prefixDel", org.apache.thrift.protocol.TType.STRUCT, (short)5); + private static final org.apache.thrift.protocol.TField TXN_FIELD_DESC = new org.apache.thrift.protocol.TField("txn", org.apache.thrift.protocol.TType.I32, (short)6); + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + HEADER((short)1, "header"), + DATA_ADD((short)2, "dataAdd"), + DATA_DEL((short)3, "dataDel"), + PREFIX_ADD((short)4, "prefixAdd"), + PREFIX_DEL((short)5, "prefixDel"), + /** + * + * @see PatchTxn + */ + TXN((short)6, "txn"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // HEADER + return HEADER; + case 2: // DATA_ADD + return DATA_ADD; + case 3: // DATA_DEL + return DATA_DEL; + case 4: // PREFIX_ADD + return PREFIX_ADD; + case 5: // PREFIX_DEL + return PREFIX_DEL; + case 6: // TXN + return TXN; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Patch_Header.class))); + tmpMap.put(_Fields.DATA_ADD, new org.apache.thrift.meta_data.FieldMetaData("dataAdd", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Patch_Data_Add.class))); + tmpMap.put(_Fields.DATA_DEL, new org.apache.thrift.meta_data.FieldMetaData("dataDel", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Patch_Data_Del.class))); + tmpMap.put(_Fields.PREFIX_ADD, new org.apache.thrift.meta_data.FieldMetaData("prefixAdd", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Patch_Prefix_Add.class))); + tmpMap.put(_Fields.PREFIX_DEL, new org.apache.thrift.meta_data.FieldMetaData("prefixDel", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Patch_Prefix_Del.class))); + tmpMap.put(_Fields.TXN, new org.apache.thrift.meta_data.FieldMetaData("txn", org.apache.thrift.TFieldRequirementType.OPTIONAL, + new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, PatchTxn.class))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(RDF_Patch_Row.class, metaDataMap); + } + + public RDF_Patch_Row() { + super(); + } + + public RDF_Patch_Row(_Fields setField, java.lang.Object value) { + super(setField, value); + } + + public RDF_Patch_Row(RDF_Patch_Row other) { + super(other); + } + public RDF_Patch_Row deepCopy() { + return new RDF_Patch_Row(this); + } + + public static RDF_Patch_Row header(Patch_Header value) { + RDF_Patch_Row x = new RDF_Patch_Row(); + x.setHeader(value); + return x; + } + + public static RDF_Patch_Row dataAdd(Patch_Data_Add value) { + RDF_Patch_Row x = new RDF_Patch_Row(); + x.setDataAdd(value); + return x; + } + + public static RDF_Patch_Row dataDel(Patch_Data_Del value) { + RDF_Patch_Row x = new RDF_Patch_Row(); + x.setDataDel(value); + return x; + } + + public static RDF_Patch_Row prefixAdd(Patch_Prefix_Add value) { + RDF_Patch_Row x = new RDF_Patch_Row(); + x.setPrefixAdd(value); + return x; + } + + public static RDF_Patch_Row prefixDel(Patch_Prefix_Del value) { + RDF_Patch_Row x = new RDF_Patch_Row(); + x.setPrefixDel(value); + return x; + } + + public static RDF_Patch_Row txn(PatchTxn value) { + RDF_Patch_Row x = new RDF_Patch_Row(); + x.setTxn(value); + return x; + } + + + @Override + protected void checkType(_Fields setField, java.lang.Object value) throws java.lang.ClassCastException { + switch (setField) { + case HEADER: + if (value instanceof Patch_Header) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type Patch_Header for field 'header', but got " + value.getClass().getSimpleName()); + case DATA_ADD: + if (value instanceof Patch_Data_Add) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type Patch_Data_Add for field 'dataAdd', but got " + value.getClass().getSimpleName()); + case DATA_DEL: + if (value instanceof Patch_Data_Del) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type Patch_Data_Del for field 'dataDel', but got " + value.getClass().getSimpleName()); + case PREFIX_ADD: + if (value instanceof Patch_Prefix_Add) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type Patch_Prefix_Add for field 'prefixAdd', but got " + value.getClass().getSimpleName()); + case PREFIX_DEL: + if (value instanceof Patch_Prefix_Del) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type Patch_Prefix_Del for field 'prefixDel', but got " + value.getClass().getSimpleName()); + case TXN: + if (value instanceof PatchTxn) { + break; + } + throw new java.lang.ClassCastException("Was expecting value of type PatchTxn for field 'txn', but got " + value.getClass().getSimpleName()); + default: + throw new java.lang.IllegalArgumentException("Unknown field id " + setField); + } + } + + @Override + protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TField field) throws org.apache.thrift.TException { + _Fields setField = _Fields.findByThriftId(field.id); + if (setField != null) { + switch (setField) { + case HEADER: + if (field.type == HEADER_FIELD_DESC.type) { + Patch_Header header; + header = new Patch_Header(); + header.read(iprot); + return header; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + case DATA_ADD: + if (field.type == DATA_ADD_FIELD_DESC.type) { + Patch_Data_Add dataAdd; + dataAdd = new Patch_Data_Add(); + dataAdd.read(iprot); + return dataAdd; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + case DATA_DEL: + if (field.type == DATA_DEL_FIELD_DESC.type) { + Patch_Data_Del dataDel; + dataDel = new Patch_Data_Del(); + dataDel.read(iprot); + return dataDel; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + case PREFIX_ADD: + if (field.type == PREFIX_ADD_FIELD_DESC.type) { + Patch_Prefix_Add prefixAdd; + prefixAdd = new Patch_Prefix_Add(); + prefixAdd.read(iprot); + return prefixAdd; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + case PREFIX_DEL: + if (field.type == PREFIX_DEL_FIELD_DESC.type) { + Patch_Prefix_Del prefixDel; + prefixDel = new Patch_Prefix_Del(); + prefixDel.read(iprot); + return prefixDel; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + case TXN: + if (field.type == TXN_FIELD_DESC.type) { + PatchTxn txn; + txn = org.apache.jena.riot.thrift.wire.PatchTxn.findByValue(iprot.readI32()); + return txn; + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + default: + throw new java.lang.IllegalStateException("setField wasn't null, but didn't match any of the case statements!"); + } + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type); + return null; + } + } + + @Override + protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + switch (setField_) { + case HEADER: + Patch_Header header = (Patch_Header)value_; + header.write(oprot); + return; + case DATA_ADD: + Patch_Data_Add dataAdd = (Patch_Data_Add)value_; + dataAdd.write(oprot); + return; + case DATA_DEL: + Patch_Data_Del dataDel = (Patch_Data_Del)value_; + dataDel.write(oprot); + return; + case PREFIX_ADD: + Patch_Prefix_Add prefixAdd = (Patch_Prefix_Add)value_; + prefixAdd.write(oprot); + return; + case PREFIX_DEL: + Patch_Prefix_Del prefixDel = (Patch_Prefix_Del)value_; + prefixDel.write(oprot); + return; + case TXN: + PatchTxn txn = (PatchTxn)value_; + oprot.writeI32(txn.getValue()); + return; + default: + throw new java.lang.IllegalStateException("Cannot write union with unknown field " + setField_); + } + } + + @Override + protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProtocol iprot, short fieldID) throws org.apache.thrift.TException { + _Fields setField = _Fields.findByThriftId(fieldID); + if (setField != null) { + switch (setField) { + case HEADER: + Patch_Header header; + header = new Patch_Header(); + header.read(iprot); + return header; + case DATA_ADD: + Patch_Data_Add dataAdd; + dataAdd = new Patch_Data_Add(); + dataAdd.read(iprot); + return dataAdd; + case DATA_DEL: + Patch_Data_Del dataDel; + dataDel = new Patch_Data_Del(); + dataDel.read(iprot); + return dataDel; + case PREFIX_ADD: + Patch_Prefix_Add prefixAdd; + prefixAdd = new Patch_Prefix_Add(); + prefixAdd.read(iprot); + return prefixAdd; + case PREFIX_DEL: + Patch_Prefix_Del prefixDel; + prefixDel = new Patch_Prefix_Del(); + prefixDel.read(iprot); + return prefixDel; + case TXN: + PatchTxn txn; + txn = org.apache.jena.riot.thrift.wire.PatchTxn.findByValue(iprot.readI32()); + return txn; + default: + throw new java.lang.IllegalStateException("setField wasn't null, but didn't match any of the case statements!"); + } + } else { + throw new org.apache.thrift.protocol.TProtocolException("Couldn't find a field with field id " + fieldID); + } + } + + @Override + protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + switch (setField_) { + case HEADER: + Patch_Header header = (Patch_Header)value_; + header.write(oprot); + return; + case DATA_ADD: + Patch_Data_Add dataAdd = (Patch_Data_Add)value_; + dataAdd.write(oprot); + return; + case DATA_DEL: + Patch_Data_Del dataDel = (Patch_Data_Del)value_; + dataDel.write(oprot); + return; + case PREFIX_ADD: + Patch_Prefix_Add prefixAdd = (Patch_Prefix_Add)value_; + prefixAdd.write(oprot); + return; + case PREFIX_DEL: + Patch_Prefix_Del prefixDel = (Patch_Prefix_Del)value_; + prefixDel.write(oprot); + return; + case TXN: + PatchTxn txn = (PatchTxn)value_; + oprot.writeI32(txn.getValue()); + return; + default: + throw new java.lang.IllegalStateException("Cannot write union with unknown field " + setField_); + } + } + + @Override + protected org.apache.thrift.protocol.TField getFieldDesc(_Fields setField) { + switch (setField) { + case HEADER: + return HEADER_FIELD_DESC; + case DATA_ADD: + return DATA_ADD_FIELD_DESC; + case DATA_DEL: + return DATA_DEL_FIELD_DESC; + case PREFIX_ADD: + return PREFIX_ADD_FIELD_DESC; + case PREFIX_DEL: + return PREFIX_DEL_FIELD_DESC; + case TXN: + return TXN_FIELD_DESC; + default: + throw new java.lang.IllegalArgumentException("Unknown field id " + setField); + } + } + + @Override + protected org.apache.thrift.protocol.TStruct getStructDesc() { + return STRUCT_DESC; + } + + @Override + protected _Fields enumForId(short id) { + return _Fields.findByThriftIdOrThrow(id); + } + + @org.apache.thrift.annotation.Nullable + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + + public Patch_Header getHeader() { + if (getSetField() == _Fields.HEADER) { + return (Patch_Header)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'header' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + public void setHeader(Patch_Header value) { + setField_ = _Fields.HEADER; + value_ = java.util.Objects.requireNonNull(value,"_Fields.HEADER"); + } + + public Patch_Data_Add getDataAdd() { + if (getSetField() == _Fields.DATA_ADD) { + return (Patch_Data_Add)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'dataAdd' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + public void setDataAdd(Patch_Data_Add value) { + setField_ = _Fields.DATA_ADD; + value_ = java.util.Objects.requireNonNull(value,"_Fields.DATA_ADD"); + } + + public Patch_Data_Del getDataDel() { + if (getSetField() == _Fields.DATA_DEL) { + return (Patch_Data_Del)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'dataDel' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + public void setDataDel(Patch_Data_Del value) { + setField_ = _Fields.DATA_DEL; + value_ = java.util.Objects.requireNonNull(value,"_Fields.DATA_DEL"); + } + + public Patch_Prefix_Add getPrefixAdd() { + if (getSetField() == _Fields.PREFIX_ADD) { + return (Patch_Prefix_Add)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'prefixAdd' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + public void setPrefixAdd(Patch_Prefix_Add value) { + setField_ = _Fields.PREFIX_ADD; + value_ = java.util.Objects.requireNonNull(value,"_Fields.PREFIX_ADD"); + } + + public Patch_Prefix_Del getPrefixDel() { + if (getSetField() == _Fields.PREFIX_DEL) { + return (Patch_Prefix_Del)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'prefixDel' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + public void setPrefixDel(Patch_Prefix_Del value) { + setField_ = _Fields.PREFIX_DEL; + value_ = java.util.Objects.requireNonNull(value,"_Fields.PREFIX_DEL"); + } + + /** + * + * @see PatchTxn + */ + public PatchTxn getTxn() { + if (getSetField() == _Fields.TXN) { + return (PatchTxn)getFieldValue(); + } else { + throw new java.lang.RuntimeException("Cannot get field 'txn' because union is currently set to " + getFieldDesc(getSetField()).name); + } + } + + /** + * + * @see PatchTxn + */ + public void setTxn(PatchTxn value) { + setField_ = _Fields.TXN; + value_ = java.util.Objects.requireNonNull(value,"_Fields.TXN"); + } + + public boolean isSetHeader() { + return setField_ == _Fields.HEADER; + } + + + public boolean isSetDataAdd() { + return setField_ == _Fields.DATA_ADD; + } + + + public boolean isSetDataDel() { + return setField_ == _Fields.DATA_DEL; + } + + + public boolean isSetPrefixAdd() { + return setField_ == _Fields.PREFIX_ADD; + } + + + public boolean isSetPrefixDel() { + return setField_ == _Fields.PREFIX_DEL; + } + + + public boolean isSetTxn() { + return setField_ == _Fields.TXN; + } + + + public boolean equals(java.lang.Object other) { + if (other instanceof RDF_Patch_Row) { + return equals((RDF_Patch_Row)other); + } else { + return false; + } + } + + public boolean equals(RDF_Patch_Row other) { + return other != null && getSetField() == other.getSetField() && getFieldValue().equals(other.getFieldValue()); + } + + @Override + public int compareTo(RDF_Patch_Row other) { + int lastComparison = org.apache.thrift.TBaseHelper.compareTo(getSetField(), other.getSetField()); + if (lastComparison == 0) { + return org.apache.thrift.TBaseHelper.compareTo(getFieldValue(), other.getFieldValue()); + } + return lastComparison; + } + + + @Override + public int hashCode() { + java.util.List list = new java.util.ArrayList(); + list.add(this.getClass().getName()); + org.apache.thrift.TFieldIdEnum setField = getSetField(); + if (setField != null) { + list.add(setField.getThriftFieldId()); + java.lang.Object value = getFieldValue(); + if (value instanceof org.apache.thrift.TEnum) { + list.add(((org.apache.thrift.TEnum)getFieldValue()).getValue()); + } else { + list.add(value); + } + } + return list.hashCode(); + } + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + +} diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/DSP.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/DSP.java index 1181836b4c3..04bc38a3424 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/DSP.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/DSP.java @@ -21,6 +21,7 @@ import java.net.http.HttpClient; import java.util.Map; +import org.apache.jena.atlas.lib.FileOps; import org.apache.jena.http.HttpEnv; import org.apache.jena.http.HttpRDF; import org.apache.jena.http.Push; @@ -95,6 +96,8 @@ public DatasetGraph GET() { * This operation does not parse the file. */ public void POST(String file) { + if ( ! FileOps.exists(file) ) + throw new IllegalArgumentException("No such file: "+file); String fileExtContentType = contentTypeFromFilename(file); HttpClient hc = requestHttpClient(serviceEndpoint, serviceEndpoint); uploadQuads(hc, serviceEndpoint, file, fileExtContentType, httpHeaders, Push.POST); @@ -114,6 +117,8 @@ public void POST(DatasetGraph dataset) { * This operation does not parse the file. */ public void PUT(String file) { + if ( ! FileOps.exists(file) ) + throw new IllegalArgumentException("No such file: "+file); String fileExtContentType = contentTypeFromFilename(file); HttpClient hc = requestHttpClient(serviceEndpoint, serviceEndpoint); uploadQuads(hc, serviceEndpoint, file, fileExtContentType, httpHeaders, Push.PUT); diff --git a/jena-fuseki2/examples/config-1-mem.ttl b/jena-fuseki2/examples/config-1-mem.ttl index 5991f63cc31..74d44388856 100644 --- a/jena-fuseki2/examples/config-1-mem.ttl +++ b/jena-fuseki2/examples/config-1-mem.ttl @@ -21,9 +21,11 @@ PREFIX ja: ## The GET /dataset?query= variants fuseki:endpoint [ fuseki:operation fuseki:query ; ] ; - fuseki:endpoint [ fuseki:operation fuseki:update ; ] ; ## gsp-rw covers gsp-r and upload. + fuseki:endpoint [ fuseki:operation fuseki:update ; ] ; fuseki:endpoint [ fuseki:operation fuseki:gsp-rw ; ] ; + ## RDF Patch + fuseki:endpoint [ fuseki:operation fuseki:patch ; ] ; fuseki:endpoint [ fuseki:operation fuseki:query ; @@ -45,9 +47,10 @@ PREFIX ja: fuseki:operation fuseki:gsp-rw ; fuseki:name "data" ] ; - fuseki:endpoint [ - fuseki:operation fuseki:upload ; - fuseki:name "upload" + fuseki:endpoint [ + ## RDF Patch + fuseki:operation fuseki:patch ; + fuseki:name "patch" ] ; fuseki:dataset :dataset ; . diff --git a/jena-fuseki2/examples/config-3-dataset-endpoints.ttl b/jena-fuseki2/examples/config-3-dataset-endpoints.ttl index 50c5462749a..2930c498037 100644 --- a/jena-fuseki2/examples/config-3-dataset-endpoints.ttl +++ b/jena-fuseki2/examples/config-3-dataset-endpoints.ttl @@ -26,6 +26,7 @@ PREFIX ja: fuseki:endpoint [ fuseki:operation fuseki:gsp-r ; ] ; fuseki:endpoint [ fuseki:operation fuseki:gsp-rw ; ] ; fuseki:endpoint [ fuseki:operation fuseki:upload ; ] ; + fuseki:endpoint [ fuseki:operation fuseki:patch ; ] ; fuseki:dataset :dataset ; . diff --git a/jena-fuseki2/examples/config-tdb1.ttl b/jena-fuseki2/examples/config-tdb1.ttl index 2dfe0cb1290..7f037cd943e 100644 --- a/jena-fuseki2/examples/config-tdb1.ttl +++ b/jena-fuseki2/examples/config-tdb1.ttl @@ -38,8 +38,8 @@ PREFIX tdb1: fuseki:name "data" ] ; fuseki:endpoint [ - fuseki:operation fuseki:upload ; - fuseki:name "upload" + fuseki:operation fuseki:patch ; + fuseki:name "patch" ] ; fuseki:dataset :dataset ; . diff --git a/jena-fuseki2/examples/config-tdb2.ttl b/jena-fuseki2/examples/config-tdb2.ttl index 98e6f10f0fd..999a10e0dd8 100644 --- a/jena-fuseki2/examples/config-tdb2.ttl +++ b/jena-fuseki2/examples/config-tdb2.ttl @@ -40,8 +40,8 @@ PREFIX tdb2: fuseki:name "data" ] ; fuseki:endpoint [ - fuseki:operation fuseki:upload ; - fuseki:name "upload" + fuseki:operation fuseki:patch ; + fuseki:name "patch" ] ; fuseki:dataset :dataset_tdb2 ; . diff --git a/jena-fuseki2/examples/config-text-tdb2.ttl b/jena-fuseki2/examples/config-text-tdb2.ttl index 225a36c52de..c0ad87ab018 100644 --- a/jena-fuseki2/examples/config-text-tdb2.ttl +++ b/jena-fuseki2/examples/config-text-tdb2.ttl @@ -40,8 +40,8 @@ PREFIX text: fuseki:name "data" ] ; fuseki:endpoint [ - fuseki:operation fuseki:upload ; - fuseki:name "upload" + fuseki:operation fuseki:patch ; + fuseki:name "patch" ] ; fuseki:dataset :text_dataset ; . diff --git a/jena-fuseki2/examples/tdb2-select-graphs.ttl b/jena-fuseki2/examples/tdb2-select-graphs.ttl index 3f3bae2c701..e1f1a49f072 100644 --- a/jena-fuseki2/examples/tdb2-select-graphs.ttl +++ b/jena-fuseki2/examples/tdb2-select-graphs.ttl @@ -35,8 +35,8 @@ PREFIX tdb2: fuseki:name "data" ] ; fuseki:endpoint [ - fuseki:operation fuseki:upload ; - fuseki:name "upload" + fuseki:operation fuseki:patch ; + fuseki:name "patch" ] ; fuseki:dataset :dataset ; . diff --git a/jena-fuseki2/jena-fuseki-core/pom.xml b/jena-fuseki2/jena-fuseki-core/pom.xml index 608627c52f3..35bfcbac32d 100644 --- a/jena-fuseki2/jena-fuseki-core/pom.xml +++ b/jena-fuseki2/jena-fuseki-core/pom.xml @@ -36,6 +36,12 @@ + + org.apache.jena + jena-rdfpatch + 4.7.0-SNAPSHOT + + org.apache.jena jena-shacl diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java index 72bfaf4f8f9..b8fa7d1ef57 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java @@ -82,7 +82,8 @@ public class FusekiConfig { "query", Operation.Query, "update", Operation.Update, "data", Operation.GSP_RW, - "get", Operation.GSP_R); + "get", Operation.GSP_R, + "patch", Operation.Patch); private static Set stdDatasetRead = Set.of(Operation.Query, @@ -91,7 +92,8 @@ public class FusekiConfig { private static Set stdDatasetWrite = Set.of(Operation.Query, Operation.Update, - Operation.GSP_RW); + Operation.GSP_RW, + Operation.Patch); static { Fuseki.init(); } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/patch/PatchApplyService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/patch/PatchApplyService.java new file mode 100644 index 00000000000..91f1decfedd --- /dev/null +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/patch/PatchApplyService.java @@ -0,0 +1,193 @@ +/* + * 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. + * + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + */ + +package org.apache.jena.fuseki.patch; + +import static java.lang.String.format; +import static org.apache.jena.fuseki.servlets.ActionExecLib.incCounter; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.jena.atlas.web.ContentType; +import org.apache.jena.fuseki.server.CounterName; +import org.apache.jena.fuseki.servlets.*; +import org.apache.jena.rdfpatch.PatchException; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.changes.PatchTxnAbortException; +import org.apache.jena.rdfpatch.changes.RDFChangesApply; +import org.apache.jena.rdfpatch.changes.RDFChangesExternalTxn; +import org.apache.jena.rdfpatch.text.RDFPatchReaderText; +import org.apache.jena.riot.RiotException; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.web.HttpSC; + +/** A Fuseki service to receive and apply a patch. */ +public class PatchApplyService extends ActionREST { + static CounterName counterPatches = CounterName.register("RDFpatch-apply", "rdf-patch.apply.requests"); + static CounterName counterPatchesGood = CounterName.register("RDFpatch-apply", "rdf-patch.apply.good"); + static CounterName counterPatchesBad = CounterName.register("RDFpatch-apply", "rdf-patch.apply.bad"); + + // It's an ActionREST because it accepts POST/PATCH with a content body. + private ContentType ctPatchText = WebContent.ctPatch; + private ContentType ctPatchBinary = WebContent.ctPatchThrift; + + public PatchApplyService() { + // Counters: the standard ActionREST counters per operation are enough. + } + + @Override + public void validate(HttpAction action) { + String method = action.getRequest().getMethod(); + switch(method) { + case HttpNames.METHOD_POST: + case HttpNames.METHOD_PATCH: + break; + default: + ServletOps.errorMethodNotAllowed(method+" : Patch must use POST or PATCH"); + } + String ctStr = action.getRequest().getContentType(); + // Must be UTF-8 or unset. But this is wrong so often. + // It is less trouble to just force UTF-8. + String charset = action.getRequest().getCharacterEncoding(); + if ( charset != null && ! WebContent.charsetUTF8.equals(charset) ) + ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Charset must be omitted or UTF-8, not "+charset); + + if ( WebContent.contentTypeHTMLForm.equals(ctStr) ) { + // Both "curl --data" and "wget --post-file" + // without also using "--header" will set the Content-type to "application/x-www-form-urlencoded". + // Treat this as "unset". + ctStr = null; + } + + // If no header Content-type - assume patch-text. + ContentType contentType = ( ctStr != null ) ? ContentType.create(ctStr) : ctPatchText; + + if ( ! ctPatchText.equals(contentType) && ! ctPatchBinary.equals(contentType) ) + ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Allowed Content-types are "+ctPatchText+" or "+ctPatchBinary+", not "+ctStr); + if ( ctPatchBinary.equals(contentType) ) + ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, ctPatchBinary.getContentTypeStr()+" not supported yet"); + } + + protected void operation(HttpAction action) { + incCounter(action.getEndpoint(), counterPatches); + try { + operation$(action); + incCounter(action.getEndpoint(), counterPatchesGood) ; + } catch ( ActionErrorException ex ) { + incCounter(action.getEndpoint(), counterPatchesBad) ; + throw ex ; + } + } + + private void operation$(HttpAction action) { + action.log.info(format("[%d] RDF Patch", action.id)); + action.beginWrite(); + try { + applyRDFPatch(action, WithPatchTxn.EXTERNAL_TXN); + action.commit(); + } + catch (PatchTxnAbortException ex) { + // This is not an error in the service. + // The patch said "abort", and transactions are being managed by this code, + // so the patch processor throws TransactionAbortException if an abort is encountered. + // where one patch is one transaction. + action.abort(); + action.log.info(format("[%d] RDF Patch : abort in patch", action.id)); + } + catch (Exception ex) { + action.abort(); + throw ex; + } finally { action.end(); } + ServletOps.success(action); + } + + private enum WithPatchTxn { PATCH_TXN, EXTERNAL_TXN } + + /** Apply a patch action. + *

+ * {@code withPatchTxn} controls whether to respect the TX-TC in the patch itself, + * or whether to execute ignoring them, allowing the caller to manage the transaction, + * such as put one transaction around the whole patch. + * Abort is always + * + * @param action + * @param withPatchTxn Whether to use transaction markers in the patch or assume call is managing transactions. + */ + private void applyRDFPatch(HttpAction action, WithPatchTxn withPatchTxn) { + try { + String ct = action.getRequest().getContentType(); + // If triples or quads, maybe POST. + + InputStream input = action.getRequest().getInputStream(); + DatasetGraph dsg = action.getDataset(); + + RDFPatchReaderText pr = new RDFPatchReaderText(input); + RDFChanges changes = new RDFChangesApply(dsg); + // External transaction. Suppress patch recorded TX and TC. + if ( withPatchTxn == WithPatchTxn.EXTERNAL_TXN ) + changes = new RDFChangesExternalTxn(changes); + + pr.apply(changes); + ServletOps.success(action); + } + catch (PatchTxnAbortException ex) { + // Let this propagate to the caller. TA encountered. + throw ex; + } + catch ( PatchException ex) { throw ex; } + catch (RiotException ex) { + ServletOps.errorBadRequest("RDF Patch parse error: "+ex.getMessage()); + } + catch (IOException ex) { + ServletOps.errorBadRequest("IOException: "+ex.getMessage()); + } + } + + // ---- POST or PATCH or OPTIONS + + @Override + protected void doPost(HttpAction action) { + operation(action); + } + + @Override + protected void doPatch(HttpAction action) { + operation(action); + } + + @Override + protected void doOptions(HttpAction action) { + ActionLib.setCommonHeadersForOptions(action); + action.getResponse().setHeader(HttpNames.hAllow, "OPTIONS,POST,PATCH"); + action.getResponse().setHeader(HttpNames.hContentLength, "0"); + } + + @Override + protected void doHead(HttpAction action) { ServletOps.errorMethodNotAllowed("HEAD"); } + + @Override + protected void doPut(HttpAction action) { ServletOps.errorMethodNotAllowed("PUT"); } + + @Override + protected void doDelete(HttpAction action) { ServletOps.errorMethodNotAllowed("DELETE"); } + + @Override + protected void doGet(HttpAction action) { ServletOps.errorMethodNotAllowed("GET"); } +} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java index f1911290964..e252ed55932 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java @@ -83,6 +83,7 @@ public class FusekiVocab public static final Resource opNoOp = resource("no-op"); public static final Resource opNoOp_alt = resource("no_op"); public static final Resource opShacl = resource("shacl"); + public static final Resource opPatch = resource("patch"); // Internal private static final String stateNameActive = DataServiceStatus.ACTIVE.name; diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java index 98122a6de0c..e54f34d1e5f 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java @@ -83,6 +83,7 @@ static private Operation create(Node id, String shortName, String description) { public static final Operation Shacl = alloc(FusekiVocab.opShacl.asNode(), "SHACL", "SHACL Validation"); public static final Operation Upload = alloc(FusekiVocab.opUpload.asNode(), "upload", "File Upload"); + public static final Operation Patch = alloc(FusekiVocab.opPatch.asNode(), "patch", "RDF Patch"); public static final Operation NoOp = alloc(FusekiVocab.opNoOp.asNode(), "no-op", "No Op"); static { diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/OperationRegistry.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/OperationRegistry.java index 442db2986bc..8952b165b89 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/OperationRegistry.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/OperationRegistry.java @@ -26,6 +26,7 @@ import org.apache.jena.atlas.logging.Log; import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.patch.PatchApplyService; import org.apache.jena.fuseki.servlets.*; import org.apache.jena.riot.WebContent; @@ -46,6 +47,7 @@ public class OperationRegistry { private static final ActionService uploadServlet = new UploadRDF(); private static final ActionService gspServlet_R = new GSP_R(); private static final ActionService gspServlet_RW = new GSP_RW(); + private static final ActionService rdfPatch = new PatchApplyService(); private static final ActionService noOperation = new NoOpActionService(); private static final ActionService shaclValidation = new SHACL_Validation(); @@ -66,6 +68,7 @@ private static OperationRegistry stdConfig() { stdOpReg.register(Operation.GSP_R, null, gspServlet_R); stdOpReg.register(Operation.GSP_RW, null, gspServlet_RW); + stdOpReg.register(Operation.Patch, WebContent.contentTypePatch, rdfPatch); stdOpReg.register(Operation.Shacl, null, shaclValidation); stdOpReg.register(Operation.Upload, null, uploadServlet); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java index d1376deb753..3327d4535fd 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java @@ -46,6 +46,8 @@ , TestQuery.class , TestSPARQLProtocol.class + , TestPatchFuseki.class + // Test ping. , TestMetrics.class , TestFusekiShaclValidation.class diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java new file mode 100644 index 00000000000..503fecc157b --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java @@ -0,0 +1,169 @@ +/* + * 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. + * + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + */ + +package org.apache.jena.fuseki.main; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.http.HttpRequest.BodyPublishers; +import java.util.function.BiConsumer; + +import org.apache.jena.atlas.lib.Pair; +import org.apache.jena.fuseki.server.DataService; +import org.apache.jena.fuseki.server.Operation; +import org.apache.jena.graph.Node; +import org.apache.jena.http.HttpOp; +import org.apache.jena.rdfpatch.RDFPatch; +import org.apache.jena.rdfpatch.RDFPatchOps; +import org.apache.jena.rdfpatch.changes.RDFChangesCollector; +import org.apache.jena.riot.WebContent; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.exec.RowSet; +import org.apache.jena.sparql.exec.RowSetOps; +import org.apache.jena.sparql.exec.http.QueryExecHTTP; +import org.apache.jena.sparql.sse.SSE; +import org.junit.Test; + +public class TestPatchFuseki { + + private static Pair create(String ...patchEndpoints) { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + String dsName = "/ds"; + + DataService.Builder builder = DataService.newBuilder(dsg); + for ( String ep : patchEndpoints ) + builder.addEndpoint(Operation.Patch, ep); + DataService dataSrv = builder.build(); + + FusekiServer server = FusekiServer.create() + //.verbose(true) + .port(0) + .add(dsName, dataSrv) + .build(); + return Pair.create(server, dsg); + } + + private static RDFPatch patch1() { + RDFChangesCollector changes = new RDFChangesCollector(); + changes.add(node(":g"), node(":s"), node(":p"), node(":o")); + return changes.getRDFPatch(); + } + + private static RDFPatch patch2() { + RDFChangesCollector changes = new RDFChangesCollector(); + changes.delete(node(":g"), node(":s"), node(":p"), node(":o")); + return changes.getRDFPatch(); + } + + private static void applyPatch(String dest, RDFPatch patch) { + String body = RDFPatchOps.str(patch); + // Undo at Jena 4.3.0 + //HttpOp.httpPost(dest, DeltaFuseki.patchContentType, body); + HttpOp.httpPost(dest, WebContent.contentTypePatch, BodyPublishers.ofString(body)); + } + + private static Node node(String string) { + return SSE.parseNode(string); + } + + private static void runTest(BiConsumer action, String...epNames) { + Pair p = create(epNames); + FusekiServer server = p.getLeft(); + DatasetGraph dsg = p.getRight(); + server.start(); + String url = "http://localhost:"+server.getPort(); + try { + action.accept(dsg, url); + } finally { server.stop(); } + } + + @Test + public void apply_patch_1() { + BiConsumer action = (dsg, url) -> { + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + // Service name + applyPatch(url+"/ds/patch", patch1()); + assertTrue(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + }; + runTest(action, "patch"); + } + + @Test + public void apply_patch_2() { + BiConsumer action = (dsg, url) -> { + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + // Content type. + applyPatch(url+"/ds", patch1()); + assertTrue(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + applyPatch(url+"/ds", patch2()); + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + }; + runTest(action, ""); + } + + @Test + public void apply_patch_3() { + BiConsumer action = (dsg, url) -> { + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + applyPatch(url+"/ds/patch", patch1()); + assertTrue(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + applyPatch(url+"/ds", patch2()); + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + }; + runTest(action, "", "patch"); + } + + @Test + public void patch_standard_setup_1() { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + String dsName = "/ds"; + FusekiServer server = FusekiServer.create() + //.verbose(true) + .port(0) + .add(dsName, dsg) + .build(); + server.start(); + String url = "http://localhost:"+server.getPort(); + try { + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + applyPatch(url+"/ds/patch", patch1()); + assertTrue(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + applyPatch(url+"/ds", patch2()); + assertFalse(dsg.contains(node(":g"), node(":s"), node(":p"), node(":o"))); + } finally { server.stop(); } + } + + @Test + public void patch_config_1() { + FusekiServer server = FusekiServer.create() + //.verbose(true) + .port(0) + .parseConfigFile("testing/Config/server-patch.ttl") + .build(); + server.start(); + String url = "http://localhost:"+server.getPort(); + try { + applyPatch(url+"/ds", patch1()); + RowSet rowSet = QueryExecHTTP.service(server.datasetURL("/ds")).query("SELECT * { GRAPH ?g { ?s ?p ?o } }").select(); + long x = RowSetOps.count(rowSet); + assertEquals(1,x); + } finally { server.stop(); } + } +} diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/server-patch.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/server-patch.ttl new file mode 100644 index 00000000000..4816b2e5477 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/server-patch.ttl @@ -0,0 +1,19 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: + +PREFIX rdfs: +PREFIX ja: + +## Only patch and query + +<#service1> rdf:type fuseki:Service ; + fuseki:name "ds" ; + fuseki:endpoint [ fuseki:operation fuseki:query ]; + fuseki:endpoint [ fuseki:operation fuseki:patch ]; + fuseki:dataset <#dataset> ; +. + +<#dataset> rdf:type ja:MemoryDataset . diff --git a/jena-rdfpatch/Binary/gen-thrift b/jena-rdfpatch/Binary/gen-thrift new file mode 100755 index 00000000000..e45044df975 --- /dev/null +++ b/jena-rdfpatch/Binary/gen-thrift @@ -0,0 +1,33 @@ +#!/bin/bash +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +if [ "$#" != 1 ] +then + echo "Usage: $(basename $0) FILE" 2>&1 + exit 1 +fi + + +# Find the namespace +PKG=../src/main/java/org/apache/jena/rdfpatch/binary/thrift +rm -f "$PKG"/*.java + +## Avoid needing a dependency javax.annotations +## generated_annotations=[undated|suppress]: +## undated: suppress the date at @Generated annotations +## +thrift -r -out ../src/main/java -gen 'java:generated_annotations=suppress' "$@" + +# Also fixup for missing @Overrides. +for f in "$PKG"/*.java +do + perl -i -p -e 's/\@SuppressWarnings.*/\@SuppressWarnings("all")/' $f +done + +##Transaction.java +F="$PKG/Transaction.java" +if [ -e "$F" ] +then + sed -e 's/public int getValue/@Override public int getValue/' < $F > F + mv F $F +fi diff --git a/jena-rdfpatch/docs/rdf-patch.md b/jena-rdfpatch/docs/rdf-patch.md new file mode 100644 index 00000000000..562dfc5d4fb --- /dev/null +++ b/jena-rdfpatch/docs/rdf-patch.md @@ -0,0 +1,193 @@ +--- +layout: doc +title: RDF Patch +section: 2 +nav_text: RDF Patch +--- + +This page describes RDF Patch. An RDF Patch is a set of changes to an +[RDF dataset](https://www.w3.org/TR/rdf11-concepts/#section-dataset). + +Patches can be organised into [RDF Patch Logs](rdf-patch-logs.html) by +using the metadata header to add an identifier and to link to previous +patches. This is on top of the RDF Patch format described here. + +## Example + +This example ensures certain prefixes are in the dataset and adds some +basic triples for a new subclass of ``. + +``` +TX . +PA "rdf" "http://www.w3.org/1999/02/22-rdf-syntax-ns#" . +PA "owl" "http://www.w3.org/2002/07/owl#" . +PA "rdfs" "http://www.w3.org/2000/01/rdf-schema#" . +A . +A . +A "SubClass" . +TC . +``` + +The example above does not have the metadata for a patch log. A patch to +make the same changes suitable for an +[RDF Patch Log](rdf-patch-logs.html) entry is: + +``` +H id . +H prev . +TX . +PA "rdf" "http://www.w3.org/1999/02/22-rdf-syntax-ns#" . +PA "owl" "http://www.w3.org/2002/07/owl#" . +PA "rdfs" "http://www.w3.org/2000/01/rdf-schema#" . +A . +A . +A "SubClass" . +TC . +``` + +## Structure + +The text format for an RDF Patch is N-Triples-like: it is a series of +rows, each row ends with a `.` (DOT). The tokens on a row are keywords, +URIs, blank nodes, writen with their label (see below) or RDF Literals, +in N-triples syntax. A keyword follows the same rules as +Turtle prefix declarations without a trailing `:`. + +A line has an operation code, then some number of items depending on +the operation. + +| Operation | | +| --------- | ----------------- | +| `H` | Header | +| `TX`
`TC`
`TA` | Change block: transactions | +| `PA`
`PD`
| Change: Prefix add and delete | +| `A`
`D` | Change: Add and delete triples and quads | + +The general structure is a header (possibly empty) and a sequence of +blocks recording changes. Each change block is a transaction. + +The RDF patch has a header then a number of transactions. + +``` +header +TX +Quad, triple or prefix changes +TC or TA +``` + +Multiple transaction blocks are allowed for multiple sets of changes in one +patch. + +A binary version based on [RDF Thrift](http://afs.github.io/rdf-thrift/) will be provided +sometime. Parsing binary compared to text for N-triples achieves a x3-x4 increase in +throughput. + +### Header + +The header provides for basic information about patch. It is a series of +(key, value) pairs. + +It is better to put complex metadata in a separate file and link to it +from the header, but certain information is best kept with the patch. An example +used by Delta is to keep the identifer of the global version id of the dataset +so that patches are applied in the right order. + +Header format: +``` +H word RDFTerm . +``` +where `word` is a string in quotes, or an unquoted string (no spaces, starts with a letter, +same as a prefix without the colon). + +The header is ended by the first `TX` or the end of the patch. + + +### Transactions + +``` +TX . +TC . +``` + +These delimit a block of quad, triple and prefix changes. + +Abort, `TA` is provided so that changes can be streamed, not obliging the +application to buffer change and wait to confirm the action is +committed. + +Transactions should be applied atomically when a patch is applied. + +### Changes + +A change is an add or delete of a quad or a prefix. + +#### Prefixes + +Prefixes do not apply to the data of the patch. They are +changes to the data the patch is applied to. + +The prefix name is without the trailing colon. It can be given as a +quoted string or unquoted string (keyword) with the same limitations as +Turtle on the prefix name. + +``` +PA rdf . +``` + +`PA` is adding a prefix, `PD` is deleting a prefix. + +#### Quads and Triples + +Triples and quads are written like N-Quads, 3 or 4 RDF terms, +with the addition of a initial `A` or `D` for "add" or "delete". +Triples are in the order S-P-O, quads are S-P-O-G. + +Add a triple: +``` +A . +``` + +## Blank nodes + +In order to synchronize datasets, changes involving blank nodes may need +to refer to a blank node already in the data. RDF Patch deals with this +by making blank node labels refer to the "system identifier" for the +blank node. + +In this way, RDF Patch is not an "RDF Format". In all syntaxes for RDF +(Turle, TriG, RDF/XML etc), blank nodes are "document scoped" meaning +that the blank node is unique to that one time reading of the document. +A new blank node is generated every time the file is read into a graph +or dataset, and that blank node does not appear in the existing data. + +In practice, most RDF triplestores, have some kind of internal +identifier that identifies th blank node. RDF Patch requires a "system +identifier" for blank nodes so that change can refer to an existing +blank node in the data. + +These can be written as `_:label` or `<_:label>` (the latter provides a +wider set of permissible characters in the label). Note that `_` is +illegal as a IRI scheme to highlight the fact this is not, stricitly, an +IRI. + +RDF 1.1 describes +[_skolemization_](https://www.w3.org/TR/rdf11-concepts/#section-skolemization) +where blank nodes are replaced by a URI. A system could use those for +RDF Patch if it also meets the additonal requirements to be able to +receive and reverse the mapping back to the internal blank node object +and also that all system generating patches can safely generate new, +fresh skolem IRIs that will become new blank nodes in the RDF dataset +then a patch is applied to it. + +## Preferred Style + +The preferred style is to write patch rows on a single line, single +space between tokens on a row and a single space before the terminal +`.`. No comments should be included (comments start `#` and run to end of +line). + +Headers should be placed before the item they refer to; for information +used by an RDF Patch Log, the metadata is about the whole patch and +should be at the start of the file, before any `TX`. + + diff --git a/jena-rdfpatch/pom.xml b/jena-rdfpatch/pom.xml new file mode 100644 index 00000000000..91b558c4b40 --- /dev/null +++ b/jena-rdfpatch/pom.xml @@ -0,0 +1,60 @@ + + + + + + 4.0.0 + jena-rdfpatch + jar + + Apache Jena - RDF Patch + + org.apache.jena + jena + 4.7.0-SNAPSHOT + .. + + + + org.apache.jena.rdfpatch + + + + + org.apache.jena + apache-jena-libs + 4.7.0-SNAPSHOT + pom + + + + org.apache.logging.log4j + log4j-slf4j-impl + test + + + + junit + junit + test + + + + diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchException.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchException.java new file mode 100644 index 00000000000..a0ecea3d3ed --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchException.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; +import org.apache.jena.riot.RiotException; +public class PatchException extends RiotException { + public PatchException() { super() ; } + public PatchException(String msg) { super(msg) ; } + public PatchException(Throwable th) { super(th) ; } + public PatchException(String msg, Throwable th) { super(msg, th) ; } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchHeader.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchHeader.java new file mode 100644 index 00000000000..0bd2e063cc3 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchHeader.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import java.util.Locale; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.apache.jena.graph.Node; + +/** RDF Patch header */ +public class PatchHeader { + // Currently, a read-only wrapper on a Map + private Map header; + + public PatchHeader(Map header) { + // Isolate and lower case + this.header = header.entrySet() + .stream().collect(Collectors.toMap(e->lc(e.getKey()), + e->e.getValue())); + } + + public Node getId() { + return get(RDFPatchConst.ID); + } + + public Node getPrevious() { + Node n = get(RDFPatchConst.PREV); + if ( n == null ) + n = get(RDFPatch.PREVIOUS); + return n; + } + + public Node get(String field) { + return header.get(lc(field)); + } + + public void apply(RDFChanges changes) { + // Do "H id" then "H prev" then the rest. + Node idNode = getId(); + Node prevNode = getPrevious(); + if ( idNode != null ) + changes.header(RDFPatchConst.ID, idNode); + if ( prevNode != null ) + changes.header(RDFPatchConst.PREV, prevNode); + + // Then the rest, + + forEach( (s,n) -> { + switch(s) { + case RDFPatchConst.ID: + case RDFPatchConst.PREV: + case RDFPatch.PREVIOUS: + return; + default: + changes.header(s, n); + } + }); + } + + public void forEach(BiConsumer action) { + + + header.forEach(action); + } + + private static String lc(String str) { + return str.toLowerCase(Locale.ROOT); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((header == null) ? 0 : header.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + PatchHeader other = (PatchHeader)obj; + if ( header == null ) { + if ( other.header != null ) + return false; + } else if ( !header.equals(other.header) ) + return false; + return true; + } + + @Override + public String toString() { + return header.toString(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchProcessor.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchProcessor.java new file mode 100644 index 00000000000..0d16141262d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchProcessor.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +public interface PatchProcessor { + public void apply(RDFChanges destination); +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDF2Patch.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDF2Patch.java new file mode 100644 index 00000000000..c0d23c623c4 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDF2Patch.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import org.apache.jena.graph.Triple; +import org.apache.jena.rdfpatch.changes.RDFChangesCollector; +import org.apache.jena.riot.system.StreamRDF; +import org.apache.jena.sparql.core.Quad; + +// DRY with StreamPatch. + +/** An {@link StreamRDF} that converts to patch format. */ +public class RDF2Patch implements StreamRDF { + RDFChangesCollector x = new RDFChangesCollector(); + private final RDFChanges changes; + +// public RDFPatch getRDFPatch() { return x.getRDFPatch(); } + + public RDF2Patch(RDFChanges changes) { + this.changes = changes; + } + + @Override + public void start() { + changes.txnBegin(); + } + + @Override + public void triple(Triple triple) { + changes.add(null, triple.getSubject(), triple.getPredicate(), triple.getObject()); + } + + @Override + public void quad(Quad quad) { + changes.add(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject()); + } + + @Override + public void base(String base) {} + + @Override + public void prefix(String prefix, String iri) { + changes.addPrefix(null, prefix, iri); + } + + @Override + public void finish() { + changes.txnCommit(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFChanges.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFChanges.java new file mode 100644 index 00000000000..dcf1f2d46e2 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFChanges.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.core.Quad; + +/** Interface for a stream of changes to to an RDF Dataset, or RDF Graph. + * For an RDF graph, the "graph name" will be null. + */ +public interface RDFChanges { + // Consider the triples versions. + + /** Header field. + * Headers are metadata about the changes being made. + */ + public void header(String field, Node value); + + /** + * Notification that a quad or triple is added. + * A stream of Triples outside a dataset will have null for the graph name. + * Inside an RDF Dataset, it may be more natural to use "urn:x-arq:DefaultGraph" or "urn:x-arq:DefaultGraphNode" + * in which case test with {@link Quad#isDefaultGraph(Node)}. + *

+ * It is not defined whether the add happens before or after this notification all. + */ + public void add(Node g, Node s, Node p, Node o); + +// /** +// * Notification that a triple is added. +// * A stream of Triples outside a dataset will have null for the graph name. +// */ +// public default void add(Node s, Node p, Node o) { +// add(null, s, p, o); +// } + + /** + * Notification that a quad or triple is deleted. + * A stream of Triples outside a dataset will have null for the graph name. + * Inside an RDF Dataset, it may be more natural to use "urn:x-arq:DefaultGraph" or "urn:x-arq:DefaultGraphNode" + * in which case test with {@link Quad#isDefaultGraph(Node)}. + *

+ * It is not defined whether the delete happens before or after this notification all. + */ + public void delete(Node g, Node s, Node p, Node o); + +// /** +// * Notification that a triple is deleted. +// * A stream of Triples outside a dataset will have null for the graph name. +// */ +// public default void delete(Node s, Node p, Node o) { +// delete(null, s, p, o); +// } + + /** + * Add a prefix. The graph name follows the same rules as {@link #add}. + */ + public void addPrefix(Node gn, String prefix, String uriStr); + + /** + * Delete a prefix. The graph name follows the same rules as {@link #add}. + */ + public void deletePrefix(Node gn, String prefix); + + /** Indicator that a transaction begins, or becomes a write transaction. */ + public void txnBegin(); + + /** Indicator that a transaction commits. + * If this throws an exception, the transaction will be aborted locally and not commit after all. + */ + public void txnCommit(); + + /** Indicator that a transaction aborts */ + public void txnAbort(); + + /** Segment marker. + *

+ * A segment is a number of transactions; the grouping rationale is not defined by RDF Patch. + *

+ * It might be used to indicate a logical collection of change transactions in a long stream of transactions. + *

+ * There is no guarantee it will be used. + *

+ * Segments must contain complete transactions.
+ * Segments must not span start-finish pairs. + */ + public void segment(); + + /** + * Start processing. + * The exact meaning is implementation dependent. + * This should be paired with a {@link #finish}. + */ + public void start(); + + /** + * Finish processing. + * The exact meaning is implementation dependent. + * This should be paired with a {@link #start}. + */ + public void finish(); +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java new file mode 100644 index 00000000000..c0391b6d144 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import org.apache.jena.graph.Node; + +public interface RDFPatch { + // Long name - not preferred. + static final String PREVIOUS = "previous"; + + public PatchHeader header(); + + public default Node getHeader(String field) { + return header().get(field) ; + } + + public default Node getId() { + return header().get(RDFPatchConst.ID); + } + + public default Node getPrevious() { + Node n = header().get(RDFPatchConst.PREV); + if ( n == null ) + n = header().get(PREVIOUS); + return n; + } + + /** Act on the patch by sending it to a changes processor. */ + public void apply(RDFChanges changes); + + public boolean repeatable(); +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchConst.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchConst.java new file mode 100644 index 00000000000..fb30a4af6e0 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchConst.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +public class RDFPatchConst { + /** RDF Patch file extension for text format */ + public static final String EXT = "rdfp"; + + /** RDF Patch file extension for binary (Thrift-based) format */ + public static final String EXT_B = "trp"; + + public static final String ID = "id"; + + public static final String PREV = "prev"; +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchOps.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchOps.java new file mode 100644 index 00000000000..30c0dda21ce --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchOps.java @@ -0,0 +1,338 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import java.io.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.binary.RDFChangesWriterBinary; +import org.apache.jena.rdfpatch.binary.RDFPatchReaderBinary; +import org.apache.jena.rdfpatch.changes.*; +import org.apache.jena.rdfpatch.system.DatasetGraphChanges; +import org.apache.jena.rdfpatch.system.GraphChanges; +import org.apache.jena.rdfpatch.system.RDFPatchAltHeader; +import org.apache.jena.rdfpatch.system.URNs; +import org.apache.jena.rdfpatch.text.RDFChangesWriterText; +import org.apache.jena.rdfpatch.text.RDFPatchReaderText; +import org.apache.jena.rdfpatch.text.TokenWriter; +import org.apache.jena.rdfpatch.text.TokenWriterText; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.riot.system.ErrorHandler; +import org.apache.jena.riot.system.StreamRDF; +import org.apache.jena.sparql.core.DatasetGraph; + +public class RDFPatchOps { + public static String namespace = "http://jena.apache.org/rdf-patch/"; + + /** Read an {@link RDFPatch} from a file. */ + public static RDFPatch fileToPatch(String filename) { + InputStream in = IO.openFile(filename); + return read(in); + } + + private static class RDFPatchNull implements RDFPatch { + private final PatchHeader header = new PatchHeader(Collections.emptyMap()); + @Override + public PatchHeader header() { + return header; + } + @Override + public void apply(RDFChanges changes) {} + + @Override + public boolean repeatable() { return true; } + } + + private static class RDFPatchEmpty implements RDFPatch { + // id, no previous. + private final Node id = URNs.unique(); + private final PatchHeader header = new PatchHeader(Collections.singletonMap(RDFPatchConst.ID, id)); + + RDFPatchEmpty() {} + + @Override + public PatchHeader header() { + return header; + } + @Override + public void apply(RDFChanges changes) { + header.apply(changes); + changes.txnBegin(); + changes.txnCommit(); + } + + @Override + public boolean repeatable() { + return true; + } + } + + /** A immutable "nullop" patch - no transaction, no id. */ + public static RDFPatch nullPatch() { + return new RDFPatchNull(); + } + + /** An immutable "empty" patch - a single transaction of no changes. + * Each call generates a new empty patch with a different id. + */ + public static RDFPatch emptyPatch() { + return new RDFPatchEmpty(); + } + + /** Create a brief summary of a patch. + *

+ * This function plays the patch. + */ + public static PatchSummary summary(RDFPatch patch) { + RDFChangesCounter x = new RDFChangesCounter(); + patch.apply(x); + return x.summary(); + } + + /** Make sure a patch has been read from its input. + * The returned {@link RDFPatch} is not connected + * to an external resource like an {@link InputStream}. + */ + public static RDFPatch collect(RDFPatch patch) { + if ( patch instanceof RDFChangesCollector ) + return patch; + return build( x-> patch.apply(x)); + } + + /** + * Build a patch. + */ + public static RDFPatch build(Consumer filler) { + RDFChangesCollector x = new RDFChangesCollector(); + filler.accept(x); + return x.getRDFPatch(); + } + + /** RDF data file to patch. + * The patch has no Id or Previous - see {@link #withHeader}. + */ + public static RDFPatch rdf2patch(String rdfDataFile) { + RDFChangesCollector x = new RDFChangesCollector(); + RDF2Patch dest = new RDF2Patch(x); + RDFParser.source(rdfDataFile).parse(dest); + RDFPatch patch = x.getRDFPatch(); + return patch; + } + + /** Create a patch with a specified "prev". */ + public static RDFPatch withPrev(RDFPatch body, Node prev) { + return withHeader(body, body.getId(), prev); + } + + /** Create a patch with body from "patch" and previous set to the id of "prevPatch". */ + public static RDFPatch withPrev(RDFPatch patch, RDFPatch prevPatch) { + return withPrev(patch, prevPatch.getId()); + } + + /** Create a patch with the id and prev as as given in the arguments, ignoring any header in the body patch. */ + public static RDFPatch withHeader(RDFPatch body, Node id, Node prev) { + PatchHeader h = makeHeader(id, prev); + return withHeader(h, body); + } + + /** Create a patch with the header and body as given in the arguments, ignoring any header in the body patch. */ + public static RDFPatch withHeader(PatchHeader header, RDFPatch body) { + return new RDFPatchAltHeader(header, body); + } + + /** Match a patch header with the given id and prev. Prev may be null. */ + public static PatchHeader makeHeader(Node id, Node prev) { + Objects.requireNonNull(id, "Head id node is null"); + Map m = new HashMap<>(); + m.put(RDFPatchConst.ID, id); + if ( prev != null ) + m.put(RDFPatchConst.PREV, prev); + return new PatchHeader(m); + } + + /** + * Read an {@link RDFPatch} from a file in text format + * Throws {@link PatchException} on patch parse error. + */ + public static RDFPatch read(InputStream input) { + RDFPatchReaderText pr = new RDFPatchReaderText(input); + RDFChangesCollector c = new RDFChangesCollector(); + pr.apply(c); + return c.getRDFPatch(); + } + + public static RDFPatch read(InputStream input, ErrorHandler errorHandler) { + RDFPatchReaderText pr = new RDFPatchReaderText(input, errorHandler); + RDFChangesCollector c = new RDFChangesCollector(); + pr.apply(c); + return c.getRDFPatch(); + } + + /** Read an {@link RDFPatch} from a file. */ + public static RDFPatch read(String filename) { + try ( InputStream input = IO.openFile(filename) ) { + return read(input); + } catch (IOException ex) { IO.exception(ex); return null; } + } + + /** + * Read an {@link RDFPatch} from an input stream in binary format. + */ + public static RDFPatch readBinary(InputStream input) { + PatchProcessor reader = RDFPatchReaderBinary.create(input); + RDFChangesCollector c = new RDFChangesCollector(); + reader.apply(c); + return c.getRDFPatch(); + } + + /** Read an {@link RDFPatch} from a file. */ + public static RDFPatch readBinary(String filename) { + try ( InputStream input = IO.openFile(filename) ) { + return readBinary(input); + } catch (IOException ex) { IO.exception(ex); return null; } + } + + /** Read an {@link RDFPatch} header. */ + public static PatchHeader readHeader(InputStream input) { + return RDFPatchReaderText.readerHeader(input); + } + + /** Apply changes from a {@link RDFPatch} to a {@link DatasetGraph} */ + public static void applyChange(DatasetGraph dsg, RDFPatch patch) { + RDFChanges changes = new RDFChangesApply(dsg); + patch.apply(changes); + } + + /** Apply changes from a text format input stream to a {@link DatasetGraph} */ + public static void applyChange(DatasetGraph dsg, InputStream input) { + RDFPatchReaderText pr = new RDFPatchReaderText(input); + RDFChanges changes = new RDFChangesApply(dsg); + pr.apply(changes); + } + + /** Apply changes from a {@link RDFPatch} to a {@link Graph} */ + public static void applyChange(Graph graph, RDFPatch patch) { + RDFChanges changes = new RDFChangesApplyGraph(graph); + patch.apply(changes); + } + + /** Apply changes from a text format input stream to a {@link Graph} */ + public static void applyChange(Graph graph, InputStream input) { + RDFPatchReaderText pr = new RDFPatchReaderText(input); + RDFChanges changes = new RDFChangesApplyGraph(graph); + pr.apply(changes); + } + + /** Create a {@link DatasetGraph} that sends changes to a {@link RDFChanges} stream */ + public static DatasetGraph changes(DatasetGraph dsgBase, RDFChanges changes) { + return new DatasetGraphChanges(dsgBase, changes); + } + + private static void printer(PrintStream out, String fmt, Object... args) { + out.printf(fmt, args); + if ( ! fmt.endsWith("\n") ) + out.println(); + } + + /** An {@link RDFChanges} that prints debug information to {@code System.out}. + * Output is for debugging - it is not legal text patch syntax. + */ + public static RDFChanges changesPrinter() { return new RDFChangesLog((fmt, args)->printer(System.out, fmt, args)); } + + /** + * An {@link RDFChanges} that prints RDFPatch syntax to an {@code OutputStream} in text format. + * The application must call {@code RDFChanges.start} and {@code RDFChanges.finish}. + */ + public static RDFChangesWriterText textWriter(OutputStream output) { + return RDFChangesWriterText.create(output); + } + + /** Create a {@link Graph} that sends changes to a {@link RDFChanges} stream */ + public static Graph changes(Graph graphBase, RDFChanges changes) { + return new GraphChanges(graphBase, changes); + } + + /** Create a {@link DatasetGraph} that writes changes to an {@link OutputStream} in text format. + * The caller is responsible for closing the {@link OutputStream}. + */ + public static DatasetGraph textWriter(DatasetGraph dsgBase, OutputStream out) { + RDFChanges changeLog = textWriter(out); + return changes(dsgBase, changeLog); + } + + /** Create a {@link Graph} that writes changes to an {@link OutputStream} in text format. + * The caller is responsible for closing the {@link OutputStream}. + */ + public static Graph textWriter(Graph graph, OutputStream out) { + RDFChanges changeLog = textWriter(out); + return changes(graph, changeLog); + } + + /** Write a {@link RDFPatch} in text format */ + public static void write(OutputStream out, RDFPatch patch) { + RDFChanges c = RDFChangesWriterText.create(out); + c.start(); + patch.apply(c); + c.finish(); + } + + /** Write a {@link RDFPatch} in binary format */ + public static void writeBinary(OutputStream out, RDFPatch patch) { + RDFChangesWriterBinary.write(patch, out); + } + + /** Write an {@link StreamRDF} out in {@link RDFPatch} text format. + * {@link StreamRDF#start} and {@link StreamRDF#finish} + * must be called; these bracket the patch in transaction markers + * {@code TX} and {@code TC}. + */ + public static StreamRDF write(OutputStream out) { + RDFChanges rdfChanges = RDFChangesWriterText.create(out); + return new StreamPatch(rdfChanges); + } + + /** Provide an {@link StreamRDF} that will output in RDFPatch binary format. + * {@link StreamRDF#start} and {@link StreamRDF#finish} + * must be called; these bracket the patch in transaction markers + * {@code TX} and {@code TC}. + */ + public static void writeBinary(OutputStream out, Consumer action) { + RDFChangesWriterBinary.writeBinary(out, c->{ + StreamRDF stream = new StreamPatch(c); + action.accept(stream); + }); + } + + public static String str(RDFPatch patch) { + StringWriter sw = new StringWriter(); + TokenWriter tw = TokenWriterText.create(sw); + RDFChanges c = new RDFChangesWriterText(tw); + patch.apply(c); + tw.flush(); + return sw.toString(); + } + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchWrapper.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchWrapper.java new file mode 100644 index 00000000000..2f694da7313 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatchWrapper.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +public class RDFPatchWrapper implements RDFPatch { + + private final RDFPatch other; + protected final RDFPatch get() { return other; } + + public RDFPatchWrapper(RDFPatch other) { + this.other = other; + } + + @Override + public PatchHeader header() { + return other.header(); + } + + @Override + public void apply(RDFChanges changes) { + other.apply(changes); + } + + @Override + public boolean repeatable() { + return other.repeatable(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/StreamPatch.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/StreamPatch.java new file mode 100644 index 00000000000..4f30e0755f0 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/StreamPatch.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import java.util.UUID; + +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.graph.Triple; +import org.apache.jena.riot.system.StreamRDF; +import org.apache.jena.sparql.core.Quad; + +/** Convert a {@link StreamRDF} to a {@link RDFPatch}. + * {@link StreamRDF#start} and {@link StreamRDF#finish} + * must be called; these bracket the patch in transaction markers + * {@code TX} and {@code TC}. + */ +public class StreamPatch implements StreamRDF { + + private int depth = 0; + private RDFChanges changes; + + public StreamPatch(RDFChanges changes) { + this.changes = changes; + } + + @Override + public void start() { + depth++; + if ( depth == 1 ) { + changes.start(); + // Header + // Node n = NodeFactory.createURI(JenaUUID.getFactory().generate().asURI()); + Node n = NodeFactory.createURI("uuid:"+UUID.randomUUID().toString()); + changes.header(RDFPatchConst.ID, n); + changes.txnBegin(); + } + } + + + @Override + public void finish() { + if ( depth == 1 ) { + changes.txnCommit(); + changes.finish(); + } + --depth; + } + + @Override + public void triple(Triple triple) { + changes.add(null, triple.getSubject(), triple.getPredicate(), triple.getObject()); + } + + @Override + public void quad(Quad quad) { + changes.add(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject()); + } + + @Override + public void base(String base) {} + + @Override + public void prefix(String prefix, String iri) { + changes.addPrefix(null, prefix, iri); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/binary/RDFChangesWriterBinary.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/binary/RDFChangesWriterBinary.java new file mode 100644 index 00000000000..9435081b32b --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/binary/RDFChangesWriterBinary.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.binary; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.function.Consumer; + +import org.apache.jena.JenaRuntime; +import org.apache.jena.atlas.io.IO; +import org.apache.jena.datatypes.xsd.XSDDatatype; +import org.apache.jena.datatypes.xsd.impl.RDFLangString; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.rdfpatch.PatchException; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.RDFPatch; +import org.apache.jena.rdfpatch.text.RDFChangesWriterText; +import org.apache.jena.riot.thrift.TRDF; +import org.apache.jena.riot.thrift.wire.*; +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TProtocol; + +/** + * Write RDF Patch in binary (thrift encoded). + *

+ * This class is not thread safe. + * + * @see RDFChangesWriterText + */ +public class RDFChangesWriterBinary implements RDFChanges { + public static void write(RDFPatch patch, String filename) { + try ( OutputStream out = IO.openOutputFile(filename) ) { + write(patch, out); + } catch (IOException ex) { IO.exception(ex); } + } + + /** Write a patch in binary. */ + public static void write(RDFPatch patch, OutputStream out) { + writeBinary(out, c -> patch.apply(c) ); + } + + /** {@link RDFChanges} that writes in binary. */ + public static void writeBinary(OutputStream out, Consumer action) { + TProtocol protocol = TRDF.protocol(out); + RDFChangesWriterBinary writer = new RDFChangesWriterBinary(protocol); + writer.start(); + action.accept(writer); + writer.finish(); + } + + /** + * Return an {@link RDFChanges} that writes in binary. Must call + * {@link RDFChanges#start()} and {@link RDFChanges#finish()}. + */ + private static RDFChanges writerBinary(OutputStream out) { + TProtocol protocol = TRDF.protocol(out); + RDFChangesWriterBinary writer = new RDFChangesWriterBinary(protocol); + return writer; + } + + + // Workspace - reused objects + private final RDF_Term tv = new RDF_Term(); + private final RDF_Term ts = new RDF_Term(); + private final RDF_Term tp = new RDF_Term(); + private final RDF_Term to = new RDF_Term(); + private final RDF_Term tg = new RDF_Term(); + private final Patch_Header header = new Patch_Header(); + private final Patch_Data_Add dataAdd = new Patch_Data_Add(); + private final Patch_Data_Del dataDel = new Patch_Data_Del(); + private final Patch_Prefix_Add prefixAdd = new Patch_Prefix_Add(); + private final Patch_Prefix_Del prefixDel = new Patch_Prefix_Del(); + private final RDF_Patch_Row row = new RDF_Patch_Row(); + + private final TProtocol protocol; + public RDFChangesWriterBinary(TProtocol protocol) { + this.protocol = protocol; + } + + private void write() { + try { row.write(protocol); } + catch (TException e) { + throw new PatchException("Thrift exception", e); + } + row.clear(); + } + + @Override + public void start() {} + + @Override + public void finish() { TRDF.flush(protocol); } + + @Override + public void header(String field, Node value) { + header.clear(); + tv.clear(); + header.setName(field); + RDFChangesWriterBinary.toThrift(value, tv); + header.setValue(tv); + row.setHeader(header); + write(); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + dataAdd.clear(); + set(g,s,p,o); + dataAdd.setS(ts); + dataAdd.setP(tp); + dataAdd.setO(to); + if ( g != null ) + dataAdd.setG(tg); + row.setDataAdd(dataAdd); + write(); + } + + private void set(Node g, Node s, Node p, Node o) { + ts.clear(); RDFChangesWriterBinary.toThrift(s, ts); + tp.clear(); RDFChangesWriterBinary.toThrift(p, tp); + to.clear(); RDFChangesWriterBinary.toThrift(o, to); + tg.clear(); + if ( g != null ) + RDFChangesWriterBinary.toThrift(g, tg); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + dataDel.clear(); + set(g,s,p,o); + dataDel.setS(ts); + dataDel.setP(tp); + dataDel.setO(to); + if ( g != null ) + dataDel.setG(tg); + row.setDataDel(dataDel); + write(); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + prefixAdd.clear(); + if ( gn != null ) { + tv.clear(); + toThrift(gn, tv); + prefixAdd.setGraphNode(tv); + } + prefixAdd.setPrefix(prefix); + prefixAdd.setIriStr(uriStr); + row.setPrefixAdd(prefixAdd); + write(); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + prefixDel.clear(); + if ( gn != null ) { + tv.clear(); + toThrift(gn, tv); + prefixDel.setGraphNode(tv); + } + prefixDel.setPrefix(prefix); + row.setPrefixDel(prefixDel); + write(); + } + + @Override + public void txnBegin() { + row.setTxn(PatchTxn.TX); + write(); + } + + @Override + public void txnCommit() { + row.setTxn(PatchTxn.TC); + write(); + } + + @Override + public void txnAbort() { + row.setTxn(PatchTxn.TA); + write(); + } + + @Override + public void segment() { + row.setTxn(PatchTxn.Segment); + write(); + } + + /*package*/ static void toThrift(Node node, RDF_Term term) { + if ( node.isURI() ) { + RDF_IRI iri = new RDF_IRI(node.getURI()); + term.setIri(iri); + return; + } + + if ( node.isBlank() ) { + RDF_BNode b = new RDF_BNode(node.getBlankNodeLabel()); + term.setBnode(b); + return; + } + + if ( node.isURI() ) { + RDF_IRI iri = new RDF_IRI(node.getURI()); + term.setIri(iri); + return; + } + + if ( node.isLiteral() ) { + String lex = node.getLiteralLexicalForm(); + String dt = node.getLiteralDatatypeURI(); + String lang = node.getLiteralLanguage(); + + // General encoding. + RDF_Literal literal = new RDF_Literal(lex); + if ( JenaRuntime.isRDF11 ) { + if ( node.getLiteralDatatype().equals(XSDDatatype.XSDstring) || + node.getLiteralDatatype().equals(RDFLangString.rdfLangString) ) + dt = null; + } + + if ( dt != null ) { + literal.setDatatype(dt); + } + if ( lang != null && ! lang.isEmpty() ) + literal.setLangtag(lang); + term.setLiteral(literal); + return; + } + + if ( node.isVariable() ) { + RDF_VAR var = new RDF_VAR(node.getName()); + term.setVariable(var); + return; + } + + if ( node.isNodeTriple() ) { + Triple triple = node.getTriple(); + + RDF_Term sTerm = new RDF_Term(); + toThrift(triple.getSubject(), sTerm); + + RDF_Term pTerm = new RDF_Term(); + toThrift(triple.getPredicate(), pTerm); + + RDF_Term oTerm = new RDF_Term(); + toThrift(triple.getObject(), oTerm); + + RDF_Triple tripleTerm = new RDF_Triple(sTerm, pTerm, oTerm); + term.setTripleTerm(tripleTerm); + return ; + } + +// if ( Node.ANY.equals(node)) { +// term.setAny(ANY); +// return; +// } + throw new PatchException("Node converstion not supported: "+node); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/binary/RDFPatchReaderBinary.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/binary/RDFPatchReaderBinary.java new file mode 100644 index 00000000000..b3c9f7a857d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/binary/RDFPatchReaderBinary.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.binary; + +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.jena.datatypes.RDFDatatype; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.graph.Triple; +import org.apache.jena.rdfpatch.*; +import org.apache.jena.rdfpatch.changes.RDFChangesCollector; +import org.apache.jena.riot.thrift.TRDF; +import org.apache.jena.riot.thrift.wire.*; +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TTransportException; + +/** + * Read a binary patch. + * @see PatchProcessor + */ +public class RDFPatchReaderBinary implements PatchProcessor { + private final InputStream input; + + private RDFPatchReaderBinary(InputStream input) { + this.input = input; + } + + @Override + public void apply(RDFChanges processor) { + read(input, processor); + } + + public static PatchProcessor create(InputStream input) { return new RDFPatchReaderBinary(input); } + + /** + * Read input stream and produce an {@link RDFPatch}. + * This operation actively reads the patch into memory. + * See {@link RDFPatchReaderBinary#read(InputStream, RDFChanges)} + * to stream it to a chnage processor. + * Create an {@code PatchReaderBinary} object with {@link RDFPatchReaderBinary#create} + * to create a delayed read processor. + * See {@link RDFPatchOps#collect} to make sure a patch has been read. + */ + public static RDFPatch read(InputStream input) { + RDFChangesCollector changes = new RDFChangesCollector(); + RDFPatchReaderBinary.read(input, changes); + return changes.getRDFPatch(); + } + + /** + * Read input stream and produce a {@link PatchHeader}. + * The stream is read during this call. + */ + public static PatchHeader readHeader(InputStream input) { + TProtocol protocol = TRDF.protocol(input); + return readHeader(protocol); + } + + /** + * Read input stream and produce a {@link PatchHeader}. + * The stream is read during this call. + */ + private static PatchHeader readHeader(TProtocol protocol) { + RDF_Patch_Row row = new RDF_Patch_Row(); + Map header = new LinkedHashMap<>(); + + for (;;) { + row.clear(); + try { row.read(protocol) ; } + catch (TTransportException e) { + if ( e.getType() == TTransportException.END_OF_FILE ) + break; + throw new PatchException("Thrift exception", e); + } + catch (TException e) { + throw new PatchException("Thrift exception", e); + } + + if ( row.isSetHeader() ) { + Patch_Header h = row.getHeader(); + Node n = RDFPatchReaderBinary.fromThrift(h.getValue()); + header.put(h.getName(), n); + continue; + } + break; + } + return new PatchHeader(header); + + } + + /** Read and apply */ + public static void read(InputStream input, RDFChanges changes) { + read(TRDF.protocol(input), changes); + } + + public static void read(TProtocol protocol, RDFChanges changes) { + RDF_Patch_Row row = new RDF_Patch_Row(); + changes.start(); + for (;;) { + row.clear(); + try { row.read(protocol) ; } + catch (TTransportException e) { + if ( e.getType() == TTransportException.END_OF_FILE ) { + changes.finish(); + return; + } + throw new PatchException("Thrift exception", e); + } + catch (TException e) { + throw new PatchException("Thrift exception", e); + } + + dispatch(row, changes); + } + } + + private static void dispatch(RDF_Patch_Row row, RDFChanges changes) { + if ( row.isSetHeader() ) { + Patch_Header h = row.getHeader(); + Node n = RDFPatchReaderBinary.fromThrift(h.getValue()); + changes.header(h.getName(), n); + return; + } + + if ( row.isSetDataAdd() ) { + Patch_Data_Add add = row.getDataAdd(); + Node s = RDFPatchReaderBinary.fromThrift(add.getS()); + Node p = RDFPatchReaderBinary.fromThrift(add.getP()); + Node o = RDFPatchReaderBinary.fromThrift(add.getO()); + Node g = null; + if ( add.isSetG() ) + g = RDFPatchReaderBinary.fromThrift(add.getG()); + changes.add(g, s, p, o); + return; + } + + if ( row.isSetDataDel() ) { + Patch_Data_Del del = row.getDataDel(); + Node s = RDFPatchReaderBinary.fromThrift(del.getS()); + Node p = RDFPatchReaderBinary.fromThrift(del.getP()); + Node o = RDFPatchReaderBinary.fromThrift(del.getO()); + Node g = null; + if ( del.isSetG() ) + g = RDFPatchReaderBinary.fromThrift(del.getG()); + changes.delete(g, s, p, o); + return; + } + + if ( row.isSetPrefixAdd()) { + Patch_Prefix_Add add = row.getPrefixAdd(); + Node gn = null; + if ( add.isSetGraphNode() ) + gn = fromThrift(add.getGraphNode()); + changes.addPrefix(gn, add.getPrefix(), add.getIriStr()); + return; + } + + if ( row.isSetPrefixDel()) { + Patch_Prefix_Del del = row.getPrefixDel(); + Node gn = null; + if ( del.isSetGraphNode() ) + gn = fromThrift(del.getGraphNode()); + changes.deletePrefix(gn, del.getPrefix()); + return; + } + + if ( row.isSetTxn() ) { + PatchTxn txn = row.getTxn(); + switch (txn) { + case TX : changes.txnBegin(); break; + case TC : changes.txnCommit(); break; + case TA : changes.txnAbort(); break; + case Segment : changes.segment(); break; + } + return; + } + + throw new PatchException("Unrecogized :"+row); + } + + public static Node fromThrift(RDF_Term term) { + if ( term.isSetIri() ) + return NodeFactory.createURI(term.getIri().getIri()); + + if ( term.isSetBnode() ) + return NodeFactory.createBlankNode(term.getBnode().getLabel()); + + if ( term.isSetLiteral() ) { + RDF_Literal lit = term.getLiteral(); + String lex = lit.getLex(); + String dtString = null; + if ( lit.isSetDatatype() ) + dtString = lit.getDatatype(); + RDFDatatype dt = NodeFactory.getType(dtString); + + String lang = lit.getLangtag(); + return NodeFactory.createLiteral(lex, lang, dt); + } + + if ( term.isSetTripleTerm() ) { + RDF_Triple rt = term.getTripleTerm(); + Node s = fromThrift(rt.getS()) ; + Node p = fromThrift(rt.getP()) ; + Node o = fromThrift(rt.getO()) ; + Triple t = Triple.create(s, p, o) ; + return NodeFactory.createTripleNode(t); + } + + throw new PatchException("No conversion to a Node: "+term.toString()); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchCodes.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchCodes.java new file mode 100644 index 00000000000..de107be35d9 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchCodes.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +/** Text format code (start of line) */ +public class PatchCodes { + // An enum does not help. + // + // In the reader we need to do string -> dispatch in a string-switch statement. + // In the writer, we need the label to output. + + public static final String HEADER = "H"; + + public static final String ADD_DATA = "A"; + public static final String DEL_DATA = "D"; + + public static final String ADD_PREFIX = "PA"; + public static final String DEL_PREFIX = "PD"; + + public static final String TXN_BEGIN = "TX"; + public static final String TXN_COMMIT = "TC"; + public static final String TXN_ABORT = "TA"; + + public static final String SEGMENT = "Z"; + + /** Test whether the string is a known patch code */ + public static boolean isValid(String str) { + switch(str) { + case HEADER: + case ADD_DATA: case DEL_DATA: + case ADD_PREFIX: case DEL_PREFIX: + case TXN_BEGIN: case TXN_COMMIT: case TXN_ABORT: + case SEGMENT: + return true; + default: + return false; + } + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchSummary.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchSummary.java new file mode 100644 index 00000000000..28cb1694370 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchSummary.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +public class PatchSummary { + public long countStart = 0; + public long countFinish = 0; + public long countHeader = 0; + public long countAddData = 0; + public long countDeleteData = 0; + public long countAddPrefix = 0; + public long countDeletePrefix = 0; + public long countTxnBegin = 0; + public long countTxnCommit = 0; + public long countTxnAbort = 0; + public long countSegment = 0; + + public PatchSummary() {} + + public void reset() { + countStart = 0; + countFinish = 0; + countHeader = 0; + countAddData = 0; + countDeleteData = 0; + countAddPrefix = 0; + countDeletePrefix = 0; + countTxnBegin = 0; + countTxnCommit = 0; + countTxnAbort = 0; + countSegment = 0; + } + + @Override + public PatchSummary clone() { + PatchSummary other = new PatchSummary(); + other.countStart = this.countStart; + other.countFinish = this.countFinish; + other.countHeader = this.countHeader; + other.countAddData = this.countAddData; + other.countDeleteData = this.countDeleteData; + other.countAddPrefix = this.countAddPrefix; + other.countDeletePrefix = this.countDeletePrefix; + other.countTxnBegin = this.countTxnBegin; + other.countTxnCommit = this.countTxnCommit; + other.countTxnAbort = this.countTxnAbort; + other.countSegment = this.countSegment; + return other; + } + + public long getCountStart() { + return countStart; + } + + public long getCountFinish() { + return countFinish; + } + + public long getDepth() { + return countStart - countFinish; + } + + public long getCountHeader() { + return countHeader; + } + + public long getCountAddData() { + return countAddData; + } + + public long getCountDeleteData() { + return countDeleteData; + } + + public long getCountAddPrefix() { + return countAddPrefix; + } + + public long getCountDeletePrefix() { + return countDeletePrefix; + } + + public long getCountTxnBegin() { + return countTxnBegin; + } + + public long getCountTxnCommit() { + return countTxnCommit; + } + + public long getCountTxnAbort() { + return countTxnAbort; + } + + public long getCountSegment() { + return countSegment; + } +} + diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchTxnAbortException.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchTxnAbortException.java new file mode 100644 index 00000000000..7d4c11e2a11 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/PatchTxnAbortException.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.rdfpatch.PatchException; + +/** + * Exception thrown when a {@code TA} is encountered during external transaction control. + * + * @see RDFChangesExternalTxn + */ +public class PatchTxnAbortException extends PatchException {} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesApply.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesApply.java new file mode 100644 index 00000000000..a055f954110 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesApply.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.query.ReadWrite; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Quad; + +/** Apply changes to a {@link DatasetGraph} */ +public class RDFChangesApply implements RDFChanges { + + private DatasetGraph dsg; + + public RDFChangesApply(DatasetGraph dsg) { + this.dsg = dsg; + } + + @Override + public void start() {} + + @Override + public void finish() {} + + @Override + public void segment() {} + + @Override + public void header(String field, Node value) {} + + @Override + public void add(Node g, Node s, Node p, Node o) { + if ( g == null ) + g = Quad.defaultGraphNodeGenerated; + dsg.add(g, s, p, o); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + if ( g == null ) + g = Quad.defaultGraphNodeGenerated; + dsg.delete(g, s, p, o); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + Graph g = ( gn == null ) ? dsg.getDefaultGraph() : dsg.getGraph(gn); + g.getPrefixMapping().setNsPrefix(prefix, uriStr); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + Graph g = ( gn == null ) ? dsg.getDefaultGraph() : dsg.getGraph(gn); + g.getPrefixMapping().removeNsPrefix(prefix); + } + + @Override + public void txnBegin() { + dsg.begin(ReadWrite.WRITE); + } + + @Override + public void txnCommit() { + dsg.commit(); + } + + @Override + public void txnAbort() { + dsg.abort(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesApplyGraph.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesApplyGraph.java new file mode 100644 index 00000000000..10eb56ae0c8 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesApplyGraph.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.rdfpatch.PatchException; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.sparql.util.FmtUtils; + +/** Apply changes to a {@link Graph} */ +public class RDFChangesApplyGraph implements RDFChanges { + + private Graph graph; + + public RDFChangesApplyGraph(Graph graph) { + this.graph = graph; + } + + private static class QuadDataException extends PatchException { + QuadDataException(String msg) { super(msg) ; } + } + + @Override + public void start() {} + + @Override + public void finish() {} + + @Override + public void segment() {} + + @Override + public void header(String field, Node value) {} + + @Override + public void add(Node g, Node s, Node p, Node o) { + if ( g != null ) + throw new QuadDataException("Attempt to add quad data to a graph: g="+FmtUtils.stringForNode(g)); + graph.add(Triple.create(s, p, o)); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + if ( g != null ) + throw new QuadDataException("Attempt to delete quad data to a graph: g="+FmtUtils.stringForNode(g)); + graph.delete(Triple.create(s, p, o)); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + graph.getPrefixMapping().setNsPrefix(prefix, uriStr); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + graph.getPrefixMapping().removeNsPrefix(prefix); + } + + @Override + public void txnBegin() { + graph.getTransactionHandler().begin(); + } + + @Override + public void txnCommit() { + graph.getTransactionHandler().commit(); + } + + @Override + public void txnAbort() { + graph.getTransactionHandler().abort(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesBase.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesBase.java new file mode 100644 index 00000000000..11573fc9832 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesBase.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; + +public class RDFChangesBase implements RDFChanges { + // Logically, this is an abstract class. + // But to ensure every operation has a mention here, we do not add "abstract". + + @Override + public void start() {} + + @Override + public void finish() {} + + @Override + public void segment() {} + + @Override + public void header(String field, Node value) {} + + @Override + public void add(Node g, Node s, Node p, Node o) {} + + @Override + public void delete(Node g, Node s, Node p, Node o) { } + + @Override + public void addPrefix(Node graph, String prefix, String uriStr) {} + + @Override + public void deletePrefix(Node graph, String prefix) {} + + @Override + public void txnBegin() {} + + @Override + public void txnCommit() {} + + @Override + public void txnAbort() {} + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesCollector.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesCollector.java new file mode 100644 index 00000000000..492ed15b6d3 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesCollector.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import java.util.*; + +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.PatchHeader; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.RDFPatch; +import org.apache.jena.rdfpatch.items.*; + +/** Capture a stream of changes, then play it to another {@link RDFChanges} */ +public class RDFChangesCollector implements RDFChanges { + // NB begin - then set headers correctly becomes headers then begin. + // This is intentional so headers can be set after the patch log starts. + // But use with care. + private static final boolean RECORD_HEADER = false; + private Map header = new LinkedHashMap<>(); + private List actions = new LinkedList<>(); + + public static class RDFPatchStored implements RDFPatch { + private final PatchHeader header ; + private final List actions; + + public RDFPatchStored(Map header, List actions) { + this.header = new PatchHeader(header); + this.actions = actions; + } + + @Override + public PatchHeader header() { + return header; + } + + @Override + public void apply(RDFChanges changes) { + if ( ! RECORD_HEADER ) + header.apply(changes); + actions.forEach(a -> enact(a, changes)); + } + + @Override + public boolean repeatable() { + return true; + } + + public List getActions() { + return actions; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((actions == null) ? 0 : actions.hashCode()); + result = prime * result + ((header == null) ? 0 : header.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + RDFPatchStored other = (RDFPatchStored)obj; + if ( actions == null ) { + if ( other.actions != null ) + return false; + } else if ( !actions.equals(other.actions) ) + return false; + if ( header == null ) { + if ( other.header != null ) + return false; + } else if ( !header.equals(other.header) ) + return false; + return true; + }; + } + + public RDFChangesCollector() { } + + public RDFPatch getRDFPatch() { + return new RDFPatchStored(new HashMap<>(header), new ArrayList<>(actions)); + } + +// /** Play backwards, swapping adds for deletes and delete for adds */ +// public void playReverse(RDFChanges target) { +// System.err.println("playReverse: Partially implemented"); +// // More complicated - turn into transaction chunks then ... +// +// ListIteratorReverse.reverse(actions.listIterator()).forEachRemaining(a-> enactFlip(a, target)); +// } + + private void enactFlip(ChangeItem a, RDFChanges target) { + if ( a instanceof AddQuad ) { + AddQuad a2 = (AddQuad)a; + target.delete/*add*/(a2.g, a2.s, a2.p, a2.o); + return; + } + if ( a instanceof DeleteQuad ) { + DeleteQuad a2 = (DeleteQuad)a; + target.add/*delete*/(a2.g, a2.s, a2.p, a2.o); + return; + } +// if ( a instanceof AddPrefix ) { +// AddPrefix a2 = (AddPrefix)a; +// target.deletePrefix(a2.gn, a2.prefix, a2.uriStr); +// return; +// } +// if ( a instanceof DeletePrefix ) { +// DeletePrefix a2 = (DeletePrefix)a; +// target.addPrefix(a2.gn, a2.prefix); +// return; +// } + // Transaction. + enact(a, target); + } + + private static void enact(ChangeItem item, RDFChanges target) { + if ( item instanceof HeaderItem ) { + HeaderItem h = (HeaderItem)item; + target.header(h.field, h.value); + return; + } + if ( item instanceof AddQuad ) { + AddQuad a2 = (AddQuad)item; + target.add(a2.g, a2.s, a2.p, a2.o); + return; + } + if ( item instanceof DeleteQuad ) { + DeleteQuad a2 = (DeleteQuad)item; + target.delete(a2.g, a2.s, a2.p, a2.o); + return; + } + if ( item instanceof AddPrefix ) { + AddPrefix a2 = (AddPrefix)item; + target.addPrefix(a2.gn, a2.prefix, a2.uriStr); + return; + } + if ( item instanceof DeletePrefix ) { + DeletePrefix a2 = (DeletePrefix)item; + target.deletePrefix(a2.gn, a2.prefix); + return; + } + if ( item instanceof TxnBegin ) { + target.txnBegin(); + return; + } + if ( item instanceof TxnCommit ) { + target.txnCommit(); + return; + } + if ( item instanceof TxnAbort ) { + target.txnAbort(); + return; + } + if ( item instanceof Segment ) { + target.segment(); + return; + } + FmtLog.warn(RDFChangesCollector.class, "Unrecognized action: %s : %s", Lib.className(item), item); + } + + private void collect(ChangeItem object) { + actions.add(object); + } + + @Override + public void start() { + internalReset(); + } + + @Override + public void finish() { + // Do not call internalReset() here. + // The collected patch may be used after .finish() happens. + } + + @Override + public void segment() { + actions.add(new Segment()); + } + + public void reset() { + internalReset(); + } + + private void internalReset() { + header.clear(); + actions.clear(); + } + + @Override + public void header(String field, Node value) { + if ( RECORD_HEADER ) + collect(new HeaderItem(field, value)); + // And keep a copy. + header.put(field, value); + } + + protected Node header(String field) { + return header.get(field); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + collect(new AddQuad(g, s, p, o)); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + collect(new DeleteQuad(g, s, p, o)); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + collect(new AddPrefix(gn, prefix, uriStr)); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + collect(new DeletePrefix(gn, prefix)) ; + } + + @Override + public void txnBegin() { + collect(TxnBegin.object()); + } + + @Override + public void txnCommit() { + collect(TxnCommit.object()); + } + + @Override + public void txnAbort() { + collect(TxnAbort.object()); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesCounter.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesCounter.java new file mode 100644 index 00000000000..af66eec457d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesCounter.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; + +public class RDFChangesCounter implements RDFChanges { + + protected PatchSummary summary = new PatchSummary(); + + public RDFChangesCounter() {} + + public void reset() { + summary.reset(); + } + + public PatchSummary summary() { + return summary.clone(); + } + + @Override + public void start() { + summary.countStart++; + } + + @Override + public void finish() { + summary.countFinish++; + } + + @Override + public void segment() { + summary.countSegment++; + } + + @Override + public void header(String field, Node value) { + summary.countHeader++; + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + summary.countAddData++; + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + summary.countDeleteData++; + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + summary.countAddPrefix++; + } + + @Override + public void deletePrefix(Node gn, String prefix) { + summary.countDeletePrefix++; + } + + @Override + public void txnBegin() { + summary.countTxnBegin++; + } + + @Override + public void txnCommit() { + summary.countTxnCommit++; + } + + @Override + public void txnAbort() { + summary.countTxnAbort++; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesExternalTxn.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesExternalTxn.java new file mode 100644 index 00000000000..aff1094f074 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesExternalTxn.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.rdfpatch.PatchException; +import org.apache.jena.rdfpatch.RDFChanges; + +/** + * Wrapper for {@linkplain RDFChanges} that ignores transaction begin/commit in the patch + * assuming the caller is handling that, for examp, executing a batch of patches in a + * single transaction. + *

+ * On abort, throws {@linkplain PatchTxnAbortException} which is a + * {@linkplain PatchException}. If used with batched transactions, the caller does not know whether + * some patches committed and then one had a {@code TA} record. + * + */ +public class RDFChangesExternalTxn extends RDFChangesWrapper { + public RDFChangesExternalTxn(RDFChanges other) { + super(other); + } + + private int depth = 0; + private int countBegin = 0; + private int countCommit = 0; + + public int txnDepth() { + return depth; + } + + public int txnCountBegin() { + return countBegin; + } + + public int txnCountCommit() { + return countCommit; + } + + @Override + public void txnBegin() { depth++; countBegin++; } + + @Override + public void txnCommit() { depth--; countCommit++; } + + @Override + public void txnAbort() { + throw new PatchTxnAbortException(); + } + + @Override + public void segment() {} +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesLog.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesLog.java new file mode 100644 index 00000000000..0f7b72dc8a5 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesLog.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import static org.apache.jena.sparql.sse.SSE.str; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.system.Printer; + +public class RDFChangesLog implements RDFChanges { + final private Printer printer; + + public RDFChangesLog(Printer printer) { + this.printer = printer; + } + + @Override + public void start() { + print("Start"); + } + + @Override + public void finish() { + print("Finish"); + } + + @Override + public void segment() { + print("Z"); + } + + @Override + public void header(String field, Node value) { + print("H %s %s", field, str(value)); + } + + private void print(String fmt, Object... args) { + printer.print("> "+fmt, args); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + print("%-3s %s %s %s %s", "Add", strOr(g, "_"), str(s), str(p), str(o)); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + print("%-3s %s %s %s %s", "Del", strOr(g, "_"), str(s), str(p), str(o)); + } + + public static String strOr(Node n, String alt) { + if ( n == null ) + return alt; + else + return str(n); + } + + @Override + public void addPrefix(Node graph, String prefix, String uriStr) { + print("AddPrefix %s: <%s>", prefix, uriStr); + } + + @Override + public void deletePrefix(Node graph, String prefix) { + print("DelPrefix %s:", prefix); + } + + @Override + public void txnBegin() { + print("Begin"); + } + + @Override + public void txnCommit() { + print("Commit"); + } + + @Override + public void txnAbort() { + print("Abort"); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesLogSummary.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesLogSummary.java new file mode 100644 index 00000000000..b993195b574 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesLogSummary.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.ext.com.google.common.base.Objects; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFPatchConst; +import org.apache.jena.rdfpatch.system.Printer; + +public class RDFChangesLogSummary extends RDFChangesCounter { + final private Printer printer; + private Node node = null; + +// public RDFChangesLogSummary() { this(RDFChangesLog::printer) ; } + + public RDFChangesLogSummary(Printer printer) { + this.printer = printer; + } + + @Override + public void header(String field, Node value) { + if ( Objects.equal(field, RDFPatchConst.ID) ) + node = value; + super.header(field, value); + } + + // 6 chars of UUID. + private static int AbbrevUuidLen = "uuid:".length()+6; + + @Override + public void finish() { + if ( summary.getDepth() != 0 ) + return; + + String s = "unset"; + if ( node != null ) { + if ( node.isURI()) + s = node.getURI(); + else if ( node.isBlank() ) + s = node.getBlankNodeLabel(); + else + s = node.getLiteralLexicalForm(); + } + if ( s.startsWith("uuid:") && s.length() > AbbrevUuidLen ) + //s = s.substring(0, 11)+"..."; + s = s.substring(0, 11); + + printer.print("%s :: Add %d :: Del %d :: P-Add %d :: P-Del %d", + s, + summary.countAddData, + summary.countDeleteData, + summary.countAddPrefix, + summary.countDeletePrefix); + super.reset(); + } +} + diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesN.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesN.java new file mode 100644 index 00000000000..bf8bb180f20 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesN.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; + +/** + * An {@link RDFChanges} that replicates the stream of changes to N other {@link RDFChanges} streams. + */ +public class RDFChangesN implements RDFChanges +{ + /** Create a 2-way {@code RDFChangesN} */ + public static RDFChanges multi(RDFChanges sc1, RDFChanges sc2) { + if ( sc1 == null ) + return sc2; + if ( sc2 == null ) + return sc1; + if ( sc1 instanceof RDFChangesN ) { + ((RDFChangesN)sc1).add(sc2); + return sc1; + } else { + return new RDFChangesN(sc1, sc2) ; + } + } + + private final List changes = new ArrayList<>(); + public RDFChangesN(RDFChanges... changes) { + for ( RDFChanges sc : changes ) { + add(sc); + } + } + + public RDFChangesN(List changes) { + for ( RDFChanges sc : changes ) { + add(sc); + } + } + + private void add(RDFChanges sc) { + changes.add(sc); + } + + @Override + public void start() { + changes.forEach(RDFChanges::start); + } + + @Override + public void finish() { + changes.forEach(RDFChanges::finish); + } + + @Override + public void header(String field, Node value) { + changes.forEach(c->c.header(field, value)); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + changes.forEach(sc->sc.add(g,s,p,o)); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + changes.forEach(sc->sc.delete(g,s,p,o)); + } + + @Override + public void addPrefix(Node graph, String prefix, String uriStr) { + changes.forEach(sc->sc.addPrefix(graph, prefix, uriStr)); + } + + @Override + public void deletePrefix(Node graph, String prefix) { + changes.forEach(sc->sc.deletePrefix(graph, prefix)); + } + + @Override + public void txnBegin() { + changes.forEach(sc->sc.txnBegin()); + } + + @Override + public void txnCommit() { + changes.forEach(RDFChanges::txnCommit); + } + + @Override + public void txnAbort() { + changes.forEach(RDFChanges::txnAbort); + } + + @Override + public void segment() { + changes.forEach(RDFChanges::segment); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesNoOp.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesNoOp.java new file mode 100644 index 00000000000..d73b8e0fc0c --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesNoOp.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +public class RDFChangesNoOp extends RDFChangesBase { +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesOnStartFinish.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesOnStartFinish.java new file mode 100644 index 00000000000..e53da3277c8 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesOnStartFinish.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.rdfpatch.RDFChanges; + +/** An {@link RDFChanges} that adds callbacks on start/finish. */ +public class RDFChangesOnStartFinish extends RDFChangesWrapper { + private Runnable onStart; + private Runnable onFinish; + + public RDFChangesOnStartFinish(RDFChanges changes, Runnable onStart, Runnable onFinish) { + super(changes); + this.onStart = onStart; + this.onFinish = onFinish; + } + + @Override + public void start() { + if ( onStart != null ) + onStart.run(); + } + + @Override + public void finish() { + if ( onFinish != null ) + onFinish.run(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesWrapper.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesWrapper.java new file mode 100644 index 00000000000..6b698f11b97 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesWrapper.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; + +/** Wrapper for {@link RDFChanges} */ +public class RDFChangesWrapper implements RDFChanges { + + private RDFChanges other; + protected RDFChanges get() { return other ; } + + public RDFChangesWrapper(RDFChanges other) { + this.other = other; + } + + @Override + public void start() { + get().start(); + } + + @Override + public void finish() { + get().finish(); + } + + @Override + public void header(String field, Node value) { + get().header(field, value); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + get().add(g, s, p, o); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + get().delete(g, s, p, o); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + get().addPrefix(gn, prefix, uriStr); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + get().deletePrefix(gn, prefix); + } + + @Override + public void txnBegin() { + get().txnBegin(); + } + + @Override + public void txnCommit() { + get().txnCommit(); + } + + @Override + public void txnAbort() { + get().txnAbort(); + } + + @Override + public void segment() { + get().segment(); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesWriteUpdate.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesWriteUpdate.java new file mode 100644 index 00000000000..231d1c85481 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/changes/RDFChangesWriteUpdate.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.changes; + +import org.apache.jena.atlas.io.AWriter; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.riot.out.NodeFmtLib; +import org.apache.jena.riot.out.NodeFormatter; +import org.apache.jena.riot.out.NodeFormatterTTL; +import org.apache.jena.riot.system.PrefixMap; +import org.apache.jena.riot.system.PrefixMapFactory; + +/** Write data changes as SPARQL Update. + * This is just data - no prefixes. + */ +public class RDFChangesWriteUpdate implements RDFChanges { + private NodeFormatter formatter; + + private final AWriter out; + // Track prefixes. + private final PrefixMap pmap; + + public RDFChangesWriteUpdate(AWriter out) { + this.out = out; + this.pmap = PrefixMapFactory.create(); + // Without prefixes on output - set pmap to null. + // Avoid Jena 3.10.0 and earlier error that deleting prefixes does not stop abbreviation. + // Fixes in Jena 3.11.0 when "pmap" can be used. + this.formatter = new NodeFormatterTTL(null, /*pmap*/null) { + @Override + // Fix NodeFormatterTTL in Jena. + public void formatBNode(AWriter w, Node n) { + formatBNode(w, n.getBlankNodeLabel()); + } + + // Write as a URI. + @Override + public void formatBNode(AWriter w, String label) { + w.print("<_:"); + String lab = NodeFmtLib.encodeBNodeLabel(label); + w.print(lab); + w.print(">"); + } + }; + } + + @Override + public void start() { } + + @Override + public void finish() { } + + @Override + public void header(String field, Node value) { + header(); + out.print("# "); + out.print(field); + out.print(" "); + outputNode(out, value); + out.println(); + } + + private boolean doingHeader = true; + private boolean adding = false; + private boolean deleting = false; + + // Later : blocks for INSERT DATA, DELETE DATA and blocks for GRAPH + + @Override + public void add(Node g, Node s, Node p, Node o) { + notHeader(); + out.print("INSERT DATA "); + outputData(g, s, p, o); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + notHeader(); + out.print("DELETE DATA "); + outputData(g, s, p, o); + } + + private void outputData(Node g, Node s, Node p, Node o) { + out.write("{ "); + boolean writeGraph = ( g != null ); + + if ( writeGraph ) { + out.write("GRAPH "); + outputNode(out, g); + out.write(" { "); + } + outputNode(out, s); + out.write(" "); + outputNode(out, p); + out.write(" "); + outputNode(out, o); + out.write(" "); + if ( writeGraph ) + out.print("} "); + out.println(" } ;"); + } + + private void notHeader() { + if ( doingHeader ) { + //out.println(); + doingHeader = false; + } + } + + private void header() { + if ( ! doingHeader ) { + //out.println(); + doingHeader = true; + } + } + + private void outputNode(AWriter out, Node node) { + formatter.format(out, node); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + notHeader(); + out.print("# AddPrefix "); + if ( gn != null ) { + outputNode(out, gn); + out.print(" "); + } + out.print(prefix); + out.print(" <"); + out.print(uriStr); + out.print(">"); + out.println(); + out.print("PREFIX "); + out.print(prefix+": "); + out.print("<"); + out.print(uriStr); + out.print(">"); + out.println(); + pmap.add(prefix, uriStr); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + notHeader(); + pmap.delete(prefix); + out.print("# DelPrefix "); + if ( gn != null ) { + outputNode(out, gn); + out.print(" "); + } + out.print(prefix); + out.println(); + } + + @Override + public void txnBegin() { + notHeader(); + out.println("# Begin"); + } + + @Override + public void txnCommit() { + notHeader(); + out.println("# Commit"); + } + + @Override + public void txnAbort() { + notHeader(); + out.println("# Abort"); + } + + @Override + public void segment() { + notHeader(); + out.println("# Segment"); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/AssemblerFileLog.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/AssemblerFileLog.java new file mode 100644 index 00000000000..f91f48af943 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/AssemblerFileLog.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import static org.apache.jena.sparql.util.graph.GraphUtils.exactlyOneProperty; + +import java.util.List; + +import org.apache.jena.assembler.Assembler; +import org.apache.jena.assembler.Mode; +import org.apache.jena.assembler.assemblers.AssemblerBase; +import org.apache.jena.assembler.exceptions.AssemblerException; +import org.apache.jena.atlas.lib.IRILib; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.query.Dataset; +import org.apache.jena.query.DatasetFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.RDFPatchConst; +import org.apache.jena.rdfpatch.RDFPatchOps; +import org.apache.jena.rdfpatch.changes.RDFChangesN; +import org.apache.jena.rdfpatch.filelog.rotate.ManagedOutput; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.util.graph.GraphUtils; +import org.apache.jena.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Assembler for a dataset that wraps another and provides change logging to a file. + *

+ *     <#dataset> rdf:type patch:LoggedDataset;
+ *         patch:logFile "Dir/BaseFilename";
+ *         patch:logPolicy "
+ *
+ * 
+ * Policies: "date", "timestamp", "index", "rotate", "shift", "fixed" and "none". + *
    + *
  • "date" - add the date to the base name, rotate at midnight. + *
  • "timestamp" - add a timestamp, rotate on every file. + *
  • "index" - add a counter, rotate on every file. Highest numbered file is latest written. + *
  • "rotate" or "shift"- move indexed files up one; write the baseFilename. Highest numbered file is the oldest written. + *
  • "fixed" or "none" - only the base file name is used. + *
+ */ +public class AssemblerFileLog extends AssemblerBase { + private static Logger LOG = LoggerFactory.getLogger(AssemblerFileLog.class); + + public AssemblerFileLog() {} + + @Override + public Object open(Assembler a, Resource root, Mode mode) { + if ( !exactlyOneProperty(root, VocabPatch.pDataset) ) + throw new AssemblerException(root, "No dataset to be logged"); + if ( !root.hasProperty(VocabPatch.pLogFile) ) + throw new AssemblerException(root, "No log file"); + + Resource dataset = GraphUtils.getResourceValue(root, VocabPatch.pDataset); + List destLogs = GraphUtils.multiValueAsString(root, VocabPatch.pLogFile); + + String logPolicy = GraphUtils.getStringValue(root, VocabPatch.pLogPolicy); + FilePolicy policy = logPolicy == null ? FilePolicy.FIXED : FilePolicy.policy(logPolicy); + + DatasetGraph dsgBase;; + try { + Dataset dsBase = (Dataset)a.open(dataset); + dsgBase = dsBase.asDatasetGraph(); + } catch (Exception ex) { + FmtLog.error(this.getClass(), "Failed to build the dataset to adding change logging to: %s",dataset); + throw ex; + } + + RDFChanges changes = null; + + // It would be better if each file had a policy. + // patch:logFile [ patch:filename ; patch:policy ]; + // patch:logFile ("FILE" "FIXED"); + for ( String x : destLogs ) { + FmtLog.info(LOG, "Log file: '%s'", x); + if ( x.startsWith("file:") ) + x = IRILib.IRIToFilename(x); + + ManagedOutput output = OutputMgr.create(x, policy); + // -------------- + String ext = FileUtils.getFilenameExt(x); +// if ( ext.equals("gz") ) { +// String fn2 = x.substring(0, ".gz".length()); +// ext = FileUtils.getFilenameExt(fn2); +// } +// OutputStream out = IO.openOutputFile(x); + + boolean binaryPatches = ext.equalsIgnoreCase(RDFPatchConst.EXT_B); + RDFChanges sc = binaryPatches + ? null //RDFPatchOps.binaryWriter(out); + //: RDFPatchOps.textWriter(out); + : new RDFChangesManagedOutput(output); + + if ( sc == null ) + throw new AssemblerException(root, "Failed to build the output destination: "+x); + changes = RDFChangesN.multi(changes, sc); + } + DatasetGraph dsg = RDFPatchOps.changes(dsgBase, changes); + Dataset ds = DatasetFactory.wrap(dsg); + return ds; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/FilePolicy.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/FilePolicy.java new file mode 100644 index 00000000000..a487ba0374c --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/FilePolicy.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import java.util.Locale; +import java.util.Objects; + +import org.apache.jena.rdfpatch.filelog.rotate.FileRotateException; +import org.apache.jena.rdfpatch.filelog.rotate.ManagedOutput; + +/** File naming strategies for rotating files. */ +public enum FilePolicy { + /** + * Date based - "filename-yyyy-mm-dd", with nightly rollover. + */ + DATE, + /** + * Timestamp with explicit roll over by calling {@link ManagedOutput#rotate()} + * The file format is "filename-yyyy-mm-dd_hh-mm-ss". + */ + TIMESTAMP, + /** + * Files are filename-0001, filename-0002, .. and "rotate" means next index. + */ + INDEX, + /** + * Always write to a file with the base filename. On rotate, the files are shifted up + * as filename.001, filename.002, ... and the base filename used for a new file. + */ + SHIFT, + /** + * Use a fixed file + */ + FIXED + ; + + public static FilePolicy policy(String name) { + Objects.requireNonNull(name); + String nameLC = name.toLowerCase(Locale.ROOT); + switch(nameLC) { + case "date" : return DATE; + case "timestamp" : return TIMESTAMP; + case "index" : return INDEX; + case "rotate" : return SHIFT; + case "shift" : return SHIFT; + case "fixed" : return FIXED; + case "none" : return FIXED; + default: + throw new FileRotateException("Unknown policy name: "+name); + } + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/InitPatchFileLog.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/InitPatchFileLog.java new file mode 100644 index 00000000000..1133864e330 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/InitPatchFileLog.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import org.apache.jena.rdfpatch.system.InitPatch; +import org.apache.jena.sys.JenaSubsystemLifecycle; +import org.apache.jena.sys.JenaSystem; + +public class InitPatchFileLog implements JenaSubsystemLifecycle { + + public static int level = InitPatch.level+2; + + @Override + public void start() { + JenaSystem.logLifecycle("filePatchIdx.init - start"); + VocabPatch.init(); + JenaSystem.logLifecycle("filePatchIdx.init - finish"); + } + + @Override + public void stop() {} + + @Override + public int level() { return level ; } + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/OutputMgr.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/OutputMgr.java new file mode 100644 index 00000000000..8dfcfcb592f --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/OutputMgr.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import org.apache.jena.rdfpatch.filelog.rotate.ManagedOutput; +import org.apache.jena.rdfpatch.filelog.rotate.OutputFixed; +import org.apache.jena.rdfpatch.filelog.rotate.OutputManagedFile; + +/** Create a managed output stream handler for a file. */ +public class OutputMgr { + + /** Create a {@link ManagedOutput managed output stream} handler for a file. + * The {@link FilePolicy} determines how the file is rotated which + * may be automatic (such as by {@link FilePolicy#DATE}) + * or by external control ({@link ManagedOutput#rotate()}) + * @param directoryName + * @param baseFilename + * @param strategy + * @return ManagedOutput + */ + public static ManagedOutput create(String directoryName, String baseFilename, FilePolicy strategy) { + Objects.requireNonNull(directoryName); + Objects.requireNonNull(baseFilename); + Objects.requireNonNull(strategy); + return new OutputManagedFile(Paths.get(directoryName), baseFilename, strategy); + } + + /** Create a {@link ManagedOutput managed output stream} handler for a file. + * The {@link FilePolicy} determines how the file is rotated which + * may be automatic (such as by {@link FilePolicy#DATE}) + * or by external control ({@link ManagedOutput#rotate()}) + * @param directory + * @param baseFilename + * @param strategy + * @return ManagedOutput + */ + public static ManagedOutput create(Path directory, String baseFilename, FilePolicy strategy) { + Objects.requireNonNull(directory); + Objects.requireNonNull(baseFilename); + Objects.requireNonNull(strategy); + return new OutputManagedFile(directory, baseFilename, strategy); + } + + /** Create a {@link ManagedOutput managed output stream} handler for a file. + * The {@link FilePolicy} determines how the file is rotated which + * may be automatic (such as by {@link FilePolicy#DATE}) + * or by external control ({@link ManagedOutput#rotate()}) + * @param pathName + * @param strategy + * @return ManagedOutput + */ + public static ManagedOutput create(String pathName, FilePolicy strategy) { + Objects.requireNonNull(pathName); + Objects.requireNonNull(strategy); + if ( pathName.equals("-") ) + return new OutputFixed(System.out); + Path p = Paths.get(pathName).toAbsolutePath(); + return new OutputManagedFile(p.getParent(), p.getFileName().toString(), strategy); + } + + /** Create a managed output stream handler for fixed {@link OutputStream}. + * The {@code OutputStream} is fixed; there is no rotation policy. + * This is an adapter from {@code OutputStream} to {@code ManagedOutput}. + * @param outputStream + * @return ManagedOutput + */ + public static ManagedOutput create(OutputStream outputStream) { + return new OutputFixed(outputStream); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/RDFChangesManagedOutput.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/RDFChangesManagedOutput.java new file mode 100644 index 00000000000..3d8f3686066 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/RDFChangesManagedOutput.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import java.io.OutputStream; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.logging.Log; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFPatchConst; +import org.apache.jena.rdfpatch.filelog.rotate.ManagedOutput; +import org.apache.jena.rdfpatch.system.URNs; +import org.apache.jena.rdfpatch.text.RDFChangesWriterText; +import org.apache.jena.rdfpatch.text.TokenWriter; +import org.apache.jena.rdfpatch.text.TokenWriterText; + +/** Log changes to a {@link ManagedOutput}. + * {@link ManagedOutput} sections + * */ +public class RDFChangesManagedOutput extends RDFChangesWriterText { + + private final ManagedOutput managedOutput; + private OutputStream currentStream = null; + private Node previous = null; + + public RDFChangesManagedOutput(ManagedOutput output) { + super(null); + this.managedOutput = output; + } + + @Override + public void txnBegin() { + startOutput(); + super.txnBegin(); + } + + @Override + public void txnCommit() { + super.txnCommit(); + finishOutput(); + } + + @Override + public void txnAbort() { + super.txnAbort(); + finishOutput(); + } + + @Override + public void segment() { + super.segment(); + } + + private void startOutput() { + if ( currentStream != null ) { + Log.warn(this, "Already writing"); + return; + } + currentStream = managedOutput.output(); + TokenWriter tokenWriter = TokenWriterText.create(currentStream); + super.tok = tokenWriter; + Node id = URNs.unique(); + header(RDFPatchConst.ID, id); + if ( previous != null ) + header(RDFPatchConst.PREV, previous); + previous = id; + } + + private void finishOutput() { + super.tok.flush(); + IO.close(currentStream); + currentStream = null; + super.tok = null; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/VocabPatch.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/VocabPatch.java new file mode 100644 index 00000000000..6c4ba0a07b2 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/VocabPatch.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.sparql.core.assembler.AssemblerUtils; +import org.apache.jena.sparql.core.assembler.DatasetAssemblerVocab; +import org.apache.jena.tdb.assembler.Vocab; + +public class VocabPatch { + + private static final String NS = "http://jena.apache.org/rdf-patch#"; + + public static String getURI() { return NS ; } + + // Type + public static final Resource tLoggedDataset = Vocab.type(getURI(), "LoggedDataset"); + + // Add feature to another (sub) dataset. + // This is ja:dataset. + public static final Property pDataset = Vocab.property(DatasetAssemblerVocab.getURI(), "dataset"); + + // Name of the patch log. + public static final Property pPatchLog = Vocab.property(getURI(), "patchlog"); + + /** Name of a file to append change logs to. */ + public static final Property pLogFile = Vocab.property(getURI(), "log"); + + /** Name of a file rotation policy . */ + public static final Property pLogPolicy = Vocab.property(getURI(), "logPolicy"); + + private static volatile boolean initialized = false ; + + static { init() ; } + + static synchronized public void init() { + if ( initialized ) + return; + initialized = true; + } + + static { + AssemblerUtils.registerDataset(tLoggedDataset, new AssemblerFileLog()); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/CompressionPolicy.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/CompressionPolicy.java new file mode 100644 index 00000000000..c0c9106facc --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/CompressionPolicy.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +public enum CompressionPolicy { + /** No compression.*/ + NONE, + /** Compressed output stream */ + OUTPUT, + /** Compress when finished writing */ + ROTATE +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/FileMgr.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/FileMgr.java new file mode 100644 index 00000000000..5ab5ed55295 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/FileMgr.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.rdfpatch.PatchException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FileMgr { + static Logger LOG = LoggerFactory.getLogger(FileMgr.class); + + private static final int IDX_TRIES = 1000; + + public static Path freshFilename(Path directory, String filename) { + return freshFilename(directory, filename, 0, INC_SEP, "%d"); + } + + /** Find a unique file name, assumes that it will not take too many probes. + * Conceptually, ".0" is the base filename. + * This function does not create the file. + * Returns the full file name, directory included. + * + * @param directory Directory to look in + * @param filename Base file name, without index modifer + * @param startingFrom Begin probing at index + * @param format String format of the number as modifier e.g. "%03d" + */ + + public static Path freshFilename(Path directory, String filename, int startingFrom, String sep, String format) { + for ( int idx = startingFrom; idx < IDX_TRIES+startingFrom; idx++ ) { + String fn = ( idx == 0 ) ? filename : basename(filename, idx, sep, format); + Path p = directory.resolve(fn); + if ( ! Files.exists(p) ) + return p; + } + FmtLog.warn(LOG, "Failed to find a unique file extension name for "+filename); + return null; + } + + /** Find files matching a pattern. + * The pattern has groups: + *
    + *
  • 1 : the base filename. + *
  • 2 : the separator + *
  • 3 : the policy modifier. + *
+ * + * @param directory Path + * @param namebase base name of interest + * @param pattern Regex to extract the part of the filename for a {@link Filename} + * @return Unsorted List<Filename> of matches. + */ + public static List scan(Path directory, String namebase, Pattern pattern) { + // pattern must have 3 groups. + if ( ! Files.isDirectory(directory) ) { + FmtLog.error(LOG, "Not a directory: %s", directory); + throw new FileRotateException("Not a directory: "+directory.toString()); + } + List indexes = new ArrayList<>(); + // Crude filtering by Files.newDirectoryStream because we will process again to extract the parts. + try (DirectoryStream stream = Files.newDirectoryStream(directory, namebase+"*")) { + for ( Path f : stream ) { + Filename filename = fromPath(directory, f, pattern); + if ( filename != null ) + indexes.add(filename); + } + } catch (IOException ex) { + FmtLog.warn(LOG, "Can't inspect directory: (%s, %s)", directory, namebase); + throw new PatchException(ex); + } + return indexes; + } + + /** + * Find files matching a pattern and also include the base filename. + */ + public static List scanIncludeBase(Path directory, String filename, Pattern pattern) { + List filenames = scan(directory, filename, pattern); + if ( Files.exists(directory.resolve(filename)) ) { + Filename fn = new Filename(directory, filename, null, null, null); + filenames.add(fn); + } + //[gz] + return filenames; + } + + /** Create a {@link Filename} */ + private static Filename fromPath(Path directory, Path filepath, Pattern pattern) { + filepath = directory.resolve(filepath).getFileName(); + directory = directory.resolve(filepath).getParent(); + return fromPath(directory, filepath.getFileName().toString(), pattern); + } + + /** Create a {@link Filename} */ + /*package*/ public static Filename fromPath(Path directory, String fn, Pattern pattern) { + Matcher matcher = pattern.matcher(fn); + if ( ! matcher.matches() ) + return null; + if ( matcher.groupCount() != 3 ) { + FmtLog.info(LOG, "Match but wrong groups: "+fn); + return null; + } + + String basename = matcher.group(1); + String separator = matcher.group(2); + String modifier = matcher.group(3); + String compression = null; + if ( matcher.groupCount() >= 4 ) + compression = matcher.group(4); + + return new Filename(directory, basename, separator, modifier, compression); + } + + /** Shift files with a ".NNN" up by one, and move the base file to "filename.1". + * + * @param directory + * @param filename + */ + public static void shiftFiles(Path directory, String filename) { + shiftFiles(directory, filename, 1, "%d"); + } + + /** Match an incremental file (does not match the base file name). **/ + /*package*/ static Pattern patternIncremental = Pattern.compile("(.*)(\\.)(\\d+)"); + + /*package*/ static final String INC_SEP = "."; + /** Compare, and it there is no modifier, put in 0 */ + /*package*/ static Comparator cmpNumericModifier = (x,y)->{ + long vx = indexFromFilename(x); + long vy = indexFromFilename(y); + return Long.compare(vx, vy); + }; + + /*package*/ static long indexFromFilename(Filename filename) { + if ( filename.isBasename() ) + return 0; + return Long.parseLong(filename.modifier); + } + + /** + * Look for matching files of the "incremental" pattern: "filename.nnn". This + * includes the filename itself, if it exists. + */ + public static List scanForIncrement(Path directory, String filename) { + return scanIncludeBase(directory, filename, patternIncremental); + } + + /** + * @param directory + * @param filename + * @param increment + * @param format + */ + public static void shiftFiles(Path directory, String filename, int increment, String format) { + if ( increment <= 0 ) + throw new IllegalArgumentException("Increment must be positive: got "+increment); + + // Move files of the form "NAME" and "NAME.num" up + List files = scanForIncrement(directory, filename); + Collections.sort(files, cmpNumericModifier.reversed()); + // Guava: Lists.reverse(List) -- is a view. + + // Pass 1 : check the list of files (checks rebuilding file names) + for ( Filename fn : files ) { + Path src = directory.resolve(fn.asFilenameString()); + if ( ! Files.exists(src) ) + throw new FileRotateException("Does not exist: "+src); + if ( ! fn.isBasename() ) + // Check parsing. + Integer.parseInt(fn.modifier); + } + + // Pass 2 : do it. + + for ( Filename fn : files ) { + Path src1 = fn.absolute(); + Path src = directory.resolve(fn.asFilenameString()); + if ( ! Files.exists(src) ) + throw new FileRotateException("Does not exist: "+src); + long idx = fn.isBasename() ? 0L : Integer.parseInt(fn.modifier); + long idx2 = idx+increment; + String target = String.format("%s%s"+format, fn.basename, INC_SEP, idx2); + Path dst = directory.resolve(target); + try { + Files.move(src, dst); + } + catch (IOException e) { IO.exception(e); } + } + } + + + + /** Create a file name using the base and the numeric modifier, converted to a number using the format */ + /*package*/ static String basename(String base, long idx, String sep, String modFormat) { + return String.format("%s%s"+modFormat, base, sep, idx); + } + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/FileRotateException.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/FileRotateException.java new file mode 100644 index 00000000000..2efceff2288 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/FileRotateException.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +public class FileRotateException extends RuntimeException +{ + public FileRotateException() { super() ; } + public FileRotateException(String msg) { super(msg) ; } + public FileRotateException(Throwable th) { super(th) ; } + public FileRotateException(String msg, Throwable th) { super(msg, th) ; } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/Filename.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/Filename.java new file mode 100644 index 00000000000..f38d24528d6 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/Filename.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Path; +import java.util.Objects; + +import org.apache.jena.atlas.logging.FmtLog; + +/** + * A Structured filename with components: (directory, basename, separator, modifier, compression) + * some of which may be null. + * {@code separator} is between basename and modifier. + * compression is "gz", bz2" etc. + */ +public class Filename { + public final Path directory; + public final String basename; + public final String separator; + public final String modifier; + public final String compression; + private Path absolute; + + private static String SEP = "."; + + public Filename(Path directory, String basename, String separator, String modifier, String compression) { + Objects.requireNonNull(directory, "directory"); + Objects.requireNonNull(basename, "basename"); + this.directory = directory; + this.basename = basename; + + if ( 1 == countNonNulls(separator, modifier) ) { + FmtLog.warn(FileMgr.LOG, "Both separator and modifier must be set, or both be null: (s=%s, m=%s)", separator, modifier); + separator = null; + modifier = null; + } + + this.separator = separator; + this.modifier = modifier; + if ( compression != null && compression.startsWith(SEP) ) + compression.substring(SEP.length()); + this.compression = compression; + this.absolute = null; + } + + private Path toAbsolutePath() { + String fn = asFilenameString(); + return directory.resolve(fn).toAbsolutePath(); + } + + public boolean isBasename() { + return modifier==null || separator==null; + } + + public boolean isCompressed() { + return compression != null; + } + + /** As a filename, without directory. */ + public String asFilenameString() { + String fn = basename; + if ( ! isBasename() ) + fn = fn+separator+modifier; + if ( isCompressed() ) + fn = fn+SEP+compression; + return fn; + } + + /** As a absolute file system filename. */ + public Path absolute() { + if ( absolute == null ) + absolute = toAbsolutePath(); + return absolute; + } + + // display version. + @Override + public String toString() { + //String fn = directory+" "+basename; + String fn = basename; + //String MARK = "|"; + String MARK = ""; + if ( ! isBasename() ) + fn = fn + MARK + separator + MARK + modifier; + if ( isCompressed() ) + fn = fn + MARK + compression; + return fn; + } + + private static int countNonNulls(Object ... objects) { + int x = 0; + for ( Object obj : objects ) { + if ( obj != null ) + x++; + } + return x; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/ManagedOutput.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/ManagedOutput.java new file mode 100644 index 00000000000..868475e629d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/ManagedOutput.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.io.OutputStream; +import java.nio.file.Path; + +import org.apache.jena.rdfpatch.filelog.FilePolicy; +import org.apache.jena.rdfpatch.filelog.OutputMgr; + +/** Interface to managed output streams. + * + * @see OutputMgr + * @see FilePolicy + */ +public interface ManagedOutput { + /** Get an OutputStream; use with try-resources or similar usage pattern. + * Closing the OutputStream returns it to the manager. + */ + public OutputStream output(); + + /** Current output stream, or null if there hasn't been one yet */ + public OutputStream currentOutput(); + + /** The most recent output file name, only valid during an output section, else null. */ + public Path currentFilename(); + + /** The latest output file name, or null if there hasn't been one yet */ + public Path latestFilename(); + + /** Request file rotation */ + public void rotate(); + + /** Get rotation engine */ + public Roller roller(); +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputFixed.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputFixed.java new file mode 100644 index 00000000000..9e911bc702d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputFixed.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.concurrent.Semaphore; + +import org.apache.jena.atlas.RuntimeIOException; +import org.apache.jena.atlas.io.IO; + +/** Fixed OutputStream; one writer via {@code output()} at a time. */ +public class OutputFixed implements ManagedOutput { + private boolean valid = false; + private final OutputStream outputStream; + private final Semaphore sema = new Semaphore(1); + private OutputStreamManaged currentOutput = null; + + public OutputFixed(OutputStream output) { + this.outputStream = output; + } + + /** Get rotation engine */ + @Override + public Roller roller() { + return null; + } + + @Override + public Path currentFilename() { + return null; + } + + @Override + public Path latestFilename() { + return null; + } + + @Override + public OutputStream currentOutput() { + return null; + } + + @Override + public OutputStream output() { + try { + sema.acquire(); + } + catch (InterruptedException e) { + throw new RuntimeIOException(e); + } + currentOutput = new OutputStreamManaged(outputStream, (x)->this.finish()); + return currentOutput; + } + + private void finish() { + try { + outputStream.flush(); + currentOutput = null; + } + catch (IOException e) { + IO.exception(e); return; + } + if ( sema.availablePermits() == 0 ) + sema.release(); + } + + @Override + public void rotate() {} +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputManagedFile.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputManagedFile.java new file mode 100644 index 00000000000..96126e5baf1 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputManagedFile.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.io.*; +import java.nio.file.Path; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.Semaphore; + +import org.apache.jena.atlas.RuntimeIOException; +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.rdfpatch.filelog.FilePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** File-based {@link ManagedOutput} with various {@link FilePolicy} for file rotation. */ +public class OutputManagedFile implements ManagedOutput { + private static Logger LOG = LoggerFactory.getLogger(OutputManagedFile.class); + + // The file area + private final Path directory; + private final String filebase; + // Current active file, full path name. + private Path currentFilename = null; + + // One writer at a time. + private final Semaphore sema = new Semaphore(1); + // The output + private FileOutputStream fileOutput = null; + // Buffered output stream used by the caller. + private OutputStream output = null; + private OutputStreamManaged currentOutput = null; + + static DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE; + + // File Policy + private final Roller roller; + + // Number of writes this process-lifetime. + private long counter = 0; + + /* package*/ public OutputManagedFile(Path directory, String baseFilename, FilePolicy strategy) { + this.directory = directory; + this.filebase = baseFilename; + this.roller = roller(directory, baseFilename, strategy); + } + + private static Roller roller(Path directory, String baseFilename, FilePolicy strategy) { + switch ( strategy ) { + case DATE : return new RollerDate(directory, baseFilename); + case INDEX : return new RollerIndex(directory, baseFilename, "%04d"); + case SHIFT : return new RollerShifter(directory, baseFilename, "%03d"); + case TIMESTAMP : return new RollerTimestamp(directory, baseFilename); + case FIXED : return new RollerFixed(directory, baseFilename); + } + return null; + } + + /** Get rotation engine */ + @Override + public Roller roller() { + return roller; + } + + + @Override + public OutputStream currentOutput() { + return currentOutput; + } + + + @Override + public Path currentFilename() { + return currentOutput != null ? currentFilename : null; + } + + @Override + public Path latestFilename() { + return roller.latestFilename(); + } + + @Override + public OutputStream output() { + try { + sema.acquire(); + } + catch (InterruptedException e) { + throw new RuntimeIOException(e); + } + roller.startSection(); + advanceIfNecessary(); + currentOutput = new OutputStreamManaged(output, (x)->finish()); + return currentOutput; + } + + /** Force a rotation of the output file. */ + @Override + public void rotate() { + finish(); + roller.rotate(); + } + + private void finish() { + roller.finishSection(); + try { + currentOutput = null; + // Flush the BufferedOutputStream to the FileOutputStream + if ( output != null ) { + output.flush(); + // fsync the FileOutputStream to storage + fileOutput.getFD().sync(); + } + // output still valid. + } + catch (IOException e) { + e.printStackTrace(); + } + if ( sema.availablePermits() == 0 ) + sema.release(); + } + + private boolean hasActiveFile() { + return output != null; + } + + private void advanceIfNecessary() { + // Inside ownership of the semaphore. + // Other rules + if ( roller.hasExpired() ) + closeOutput(); + if ( ! hasActiveFile() ) + nextFile(); + } + + private void flushOutput() { + try { + output.flush(); + fileOutput.getFD().sync(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + private void closeOutput() { + if ( output == null ) + return; + flushOutput(); + IO.close(output); + //[gz] Compress currentFilename. + output = null; + fileOutput = null; + // keep: currentFilename + currentOutput = null; + } + + private void nextFile() { + try { + currentFilename = roller.nextFilename(); + FmtLog.debug(LOG, "Setup: %s", currentFilename); + // Must be a FileOutputStream so that getFD().sync is available. + fileOutput = new FileOutputStream(currentFilename.toString(), true); + output = new BufferedOutputStream(fileOutput); + //[gz] + } catch (FileNotFoundException ex) { + IO.exception(ex); + return; +// } catch (IOException ex) { +// IO.exception(ex); +// return; + } + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputStreamManaged.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputStreamManaged.java new file mode 100644 index 00000000000..3a2bfcbda7f --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/OutputStreamManaged.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.io.Closeable; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.function.Consumer; + +/** + * An {@link OutputStream} that implements {@link #close} as call a {@code Consumer}, + * for example, returns itself to a pool when closed. + */ +class OutputStreamManaged extends FilterOutputStream implements Closeable { + + private Consumer onClose; + + OutputStreamManaged(OutputStream output, Consumer onClose) { + super(output); + this.onClose = onClose; + } + + @Override + public void close() throws IOException { + onClose.accept(super.out); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/Roller.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/Roller.java new file mode 100644 index 00000000000..d25584f4fd2 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/Roller.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.apache.jena.rdfpatch.filelog.OutputMgr; + +/** + * Interface to a policy for rotating files. Writing to files is in "sections" - a section + * always goes into a single file; multiple sections may go into one file or several. + * Rollover only happens between sections. + *

+ * {@code startSection}, {@code finishSection} bracket + * each use of a {@link ManagedOutput} object. + * + * @see OutputMgr + */ +public interface Roller { + + /** Directory under management. */ + public Path directory(); + + /** Starting an output section. */ + public void startSection(); + + /** Finished an output section. */ + public void finishSection(); + + /** + * Latest filename, either currently being written or the last one written. + * The path includes the directory name to the file. + * Returns null if there isn't one (nothing written at this location). + */ + public Path latestFilename(); + + /** Policy says that the setup is no longer valid for a new (next) section. */ + public boolean hasExpired(); + + /** Move files on (if appropriate) **/ + public void rotate(); + + /** Generate the next filename; includes any directory name to the file. */ + public Path nextFilename(); + + /** Stream of all files, sorted into reverse order, newest to oldest. */ + public Stream files(); +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerDate.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerDate.java new file mode 100644 index 00000000000..26c1a04803c --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerDate.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.apache.jena.atlas.logging.FmtLog; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Filename policy where files are "filebase-yyyy-mm-dd". + * Rollover happens at the first new section written after local midnight. + * ("local" means "system default"). + */ +class RollerDate implements Roller { + private static final Logger LOG = LoggerFactory.getLogger(RollerDate.class); + // Date-based roll over + private final Path directory; + private final String baseFilename; + private Path latestFilename = null; + private LocalDate current = LocalDate.now(); + + /** Match a date-appended filename */ + private static final Pattern patternFilenameDate = Pattern.compile("(.*)(-)(\\d{4}-\\d{2}-\\d{2})"); + private static final String DATE_SEP = "-"; + private static final DateTimeFormatter fmtDate = DateTimeFormatter.ISO_LOCAL_DATE; + private static final Comparator cmpDate = (x,y)->{ + LocalDate xdt = filenameToDate(x); + LocalDate ydt = filenameToDate(y); + return xdt.compareTo(ydt); + }; + + private static LocalDate filenameToDate(Filename filename) { + return LocalDate.parse(filename.modifier, fmtDate); + } + + RollerDate(Path directory, String baseFilename) { + this.directory = directory; + this.baseFilename = baseFilename; + init(directory,baseFilename); + } + + private void init(Path directory, String baseFilename) { + List filenames = FileMgr.scan(directory, baseFilename, patternFilenameDate); + if ( filenames.isEmpty() ) + latestFilename = null; + else { + LocalDate dateLast = filenameToDate(Collections.max(filenames, cmpDate)); + LocalDate dateFirst = filenameToDate(Collections.min(filenames, cmpDate)); + int problems = 0; + if ( dateLast.isAfter(current)) { + problems++; + FmtLog.warn(LOG, "Latest output file is dated after today: %s > %s", dateLast, current); + } + if ( dateFirst.isAfter(current)) { + problems++; + FmtLog.warn(LOG, "First output file is dated after today: %s > %s", dateFirst, current); + } + if ( problems > 0 ) + throw new FileRotateException("Existing files dated into the future"); + latestFilename = filename(dateLast); + } + } + + @Override + public Stream files() { + List filenames = FileMgr.scan(directory, baseFilename, patternFilenameDate); + return filenames.stream().sorted(cmpDate); + } + + + @Override + public Path directory() { + return directory; + } + + @Override + public void startSection() {} + + @Override + public void finishSection() {} + + private ZoneId zoneId = ZoneId.systemDefault(); + //private ZoneId zoneId = ZoneId.of("UTC"); + + @Override + public Path latestFilename() { + return latestFilename; + } + + @Override + public boolean hasExpired() { + return ( LocalDate.now(zoneId).isAfter(current) ); + } + + @Override + public void rotate() { + // No-op. + } + + @Override + public Path nextFilename() { + LocalDate nextCurrent = LocalDate.now(zoneId); + // Same date. + if ( nextCurrent.equals(current) ) { } + current = nextCurrent; + latestFilename = filename(nextCurrent); + return latestFilename; + } + + // LocalDate to filename. + private Path filename(LocalDate timepoint) { + String fn = baseFilename + DATE_SEP + timepoint.format(fmtDate); + Path path = directory.resolve(fn); + if ( Files.exists(path) ) + FmtLog.warn(LOG, "Using existing file: "+fn); + return path; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerFixed.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerFixed.java new file mode 100644 index 00000000000..3cb85acb560 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerFixed.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +/** {@link Roller} that is a fixed file. */ +class RollerFixed implements Roller { + private final Path directory; + private final String baseFilename; + private Path filename = null; + + + RollerFixed(Path directory, String baseFilename) { + this.directory = directory; + this.baseFilename = baseFilename; + this.filename = directory.resolve(baseFilename); + } + + @Override + public Stream files() { + return Stream.of(new Filename(directory, baseFilename, null, null, null)); + } + + @Override + public Path directory() { + return directory; + } + + @Override + public void startSection() {} + + @Override + public void finishSection() {} + + @Override + public boolean hasExpired() { + return false; + } + + @Override + public void rotate() {} + + @Override + public Path latestFilename() { + return Files.exists(filename) ? filename : null; + } + @Override + public Path nextFilename() { + return filename; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerIndex.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerIndex.java new file mode 100644 index 00000000000..055d86d3bf6 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerIndex.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * Roller where the files are "0001", "0002", "0003", The files are not moved ; the next + * index in sequence is the next filename. See {@link RollerShifter} for shifting all the + * file names up and having a fixed current filename. + */ +class RollerIndex implements Roller { + // Explicit rollover. + + private final Path directory; + private final String baseFilename; + private final String indexFormat; + private Path lastFilename; + + public static Comparator cmpNumericModifier = FileMgr.cmpNumericModifier; + + private final Pattern patternFilenameIndex = Pattern.compile("(.*)("+Pattern.quote(FileMgr.INC_SEP)+")(\\d+)"); + private final String fmtModifer = "%04d"; + private static final String INC_SEP = FileMgr.INC_SEP; + + private Long currentId = null; + // Are we in a section? + // If not, the file needs to rotate on next access. + private boolean inSection = false; + + RollerIndex(Path directory, String baseFilename, String indexFormat) { + this.directory = directory; + this.baseFilename = baseFilename; + this.indexFormat = indexFormat; + init(directory,baseFilename); + } + + private void init(Path directory, String baseFilename) { + List filenames = FileMgr.scan(directory, baseFilename, patternFilenameIndex); + if ( ! filenames.isEmpty() ) { + Filename max = Collections.max(filenames, cmpNumericModifier); + currentId = Long.parseLong(max.modifier); + lastFilename = filename(currentId); + } + else { + // Before the start. + currentId = 0L; + lastFilename = null; + } + } + + @Override + public Stream files() { + List filenames = FileMgr.scan(directory, baseFilename, patternFilenameIndex); + return filenames.stream().sorted(cmpNumericModifier); + } + + @Override + public Path directory() { + return directory; + } + + @Override + public void startSection() { + inSection = true; + } + + @Override + public void finishSection() { + // Each section is in its own file + inSection = false; + } + + @Override + public Path latestFilename() { + return lastFilename; + } + + @Override + public void rotate() { + // Always rotates on nextFilename. + } + + @Override + public boolean hasExpired() { + // Always rotates on nextFilename. + return true; + } + + private long nextIndex() { + return currentId+1; + } + + @Override + public Path nextFilename() { + long idx = nextIndex(); + currentId = idx; + // XXX FileMgr.freshFilename(directory, baseFilename, (int)idx, INC_SEP, fmtModifer); + lastFilename = filename(currentId); + return lastFilename; + } + + private Path filename(Long idx) { + Objects.requireNonNull(idx); + String fn = FileMgr.basename(baseFilename, idx, INC_SEP, fmtModifer); + return directory.resolve(fn); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerShifter.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerShifter.java new file mode 100644 index 00000000000..a9425460527 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerShifter.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** Roller where the files are "base" , "base.001", "base.002", ... + * the current output file is always "base" and the files are + * shifted up on a rotate. + */ +class RollerShifter implements Roller { + private boolean valid = false; + private final Path directory; + private final String baseFilename; + private final Path filename; + private Path currentFilename; + + /** Match an incremental file (does not match the base file name). **/ + private static Pattern patternIncremental = FileMgr.patternIncremental; + private static final String INC_SEP = FileMgr.INC_SEP; + + private static final String numFmt = "%d"; + private static final Comparator cmpNumericModifier = FileMgr.cmpNumericModifier; + + + RollerShifter(Path directory, String baseFilename, String format) { + this.directory = directory; + this.baseFilename = baseFilename; + this.filename = directory.resolve(baseFilename); + init(directory, baseFilename); + } + + private void init(Path directory, String baseFilename) { + List filenames = FileMgr.scan(directory, baseFilename, patternIncremental); + if ( filenames.isEmpty() ) { + currentFilename = null; + } else { + currentFilename = filename; + //Filename max = Collections.max(filenames, cmpNumericModifier); + } + } + + @Override + public Stream files() { + List filenames = FileMgr.scan(directory, baseFilename, patternIncremental); + Collections.sort(filenames, cmpNumericModifier); + if (Files.exists(filename) ) { + Filename base = new Filename(directory, baseFilename, null, null, null); + filenames.add(base); + } + return filenames.stream(); + } + + @Override + public Path directory() { + return directory; + } + + @Override + public void startSection() {} + + @Override + public void finishSection() {} + + @Override + public Path latestFilename() { + return currentFilename; + } + + @Override + public void rotate() { + valid = false; + } + + @Override + public boolean hasExpired() { + return !valid; + } + + @Override + public Path nextFilename() { + valid = true; + FileMgr.shiftFiles(directory, baseFilename, 1, "%03d"); + currentFilename = filename; + return filename; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerTimestamp.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerTimestamp.java new file mode 100644 index 00000000000..81d0a6779fd --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/filelog/rotate/RollerTimestamp.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog.rotate; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.atlas.logging.FmtLog; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Filename policy where files are "filebase-yyyy-mm-dd_hh-mm-ss" + * and do not rollover automatically, only when prompted via {@link #rotate}. + */ +class RollerTimestamp implements Roller { + private final static Logger LOG = LoggerFactory.getLogger(RollerTimestamp.class); + private final Path directory; + private final String baseFilename; + private Path currentFilename = null; + private boolean valid = false; + private LocalDateTime lastTimestamp = null; + private Path lastAllocatedPath = null; + + /** Match a datetime-appended filename, with non-capturing optional fractional seconds. */ + private static final Pattern patternFilenameDateTime = Pattern.compile("(.*)(-)(\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(?:\\.\\d+)?)"); + private static final String DATETIME_SEP = "-"; + private static final DateTimeFormatter fmtDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + private static final Comparator cmpDateTime = (x,y)->{ + LocalDateTime xdt = filenameToDateTime(x); + LocalDateTime ydt = filenameToDateTime(y); + return xdt.compareTo(ydt); + }; + private static int RETRIES = 5; + + private static LocalDateTime filenameToDateTime(Filename filename) { + return LocalDateTime.parse(filename.modifier, fmtDateTime); + } + + RollerTimestamp(Path directory, String baseFilename) { + this.directory = directory; + this.baseFilename = baseFilename; + init(directory,baseFilename); + } + + private void init(Path directory, String baseFilename) { + LocalDateTime current = LocalDateTime.now(); + List filenames = FileMgr.scan(directory, baseFilename, patternFilenameDateTime); + if ( filenames.isEmpty() ) { + currentFilename = null; + } else { + LocalDateTime dtLast = filenameToDateTime(Collections.max(filenames, cmpDateTime)); + LocalDateTime dtFirst = filenameToDateTime(Collections.min(filenames, cmpDateTime)); + int problems = 0; + if ( dtLast.isAfter(current)) { + problems++; + FmtLog.warn(LOG, "Latest output file is timestamped after now: %s > %s", dtLast, current); + } + if ( dtFirst.isAfter(current)) { + problems++; + FmtLog.warn(LOG, "First output file is timestamped after now: %s > %s", dtFirst, current); + } + if ( problems > 0 ) + throw new FileRotateException("Existing files timestamped into the future"); + currentFilename = filename(dtLast); + } + } + + @Override + public Stream files() { + List filenames = FileMgr.scan(directory, baseFilename, patternFilenameDateTime); + return filenames.stream().sorted(cmpDateTime); + } + + @Override + public Path directory() { + return directory; + } + + @Override + public void startSection() {} + + @Override + public void finishSection() {} + + @Override + public Path latestFilename() { + return lastAllocatedPath; + } + + @Override + public boolean hasExpired() { + // Manual rollover only. + return ! valid; + } + + @Override + public void rotate() { + valid = false; + } + + @Override + public Path nextFilename() { + for ( int i = 1 ; ; i++ ) { + // This "must" be unique unless it is the same time as last time + // because time moves forward and we checked for future files in init(). + LocalDateTime timestamp = LocalDateTime.now(); + String fn = baseFilename + DATETIME_SEP + timestamp.format(fmtDateTime); + Path path = directory.resolve(fn); + if ( ! Files.exists(path) ) { + valid = true; + lastTimestamp = timestamp; + lastAllocatedPath = path; + return lastAllocatedPath; + } + // Try again. + if ( i == RETRIES) + throw new FileRotateException("Failed to find a new, fresh filename: "+timestamp); + Lib.sleep(1000); + } + } + + private Path filename(LocalDateTime dateTime) { + String fn = baseFilename + DATETIME_SEP + dateTime.format(fmtDateTime); + return directory.resolve(fn); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/AddPrefix.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/AddPrefix.java new file mode 100644 index 00000000000..730d1157553 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/AddPrefix.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +import org.apache.jena.graph.Node; + +public class AddPrefix extends ChangeItem { + public final Node gn; + public final String prefix; + public final String uriStr; + + public AddPrefix(Node gn, String prefix, String uriStr) { + this.gn = gn; + this.prefix = prefix; + this.uriStr = uriStr; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((gn == null) ? 0 : gn.hashCode()); + result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); + result = prime * result + ((uriStr == null) ? 0 : uriStr.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + AddPrefix other = (AddPrefix)obj; + if ( gn == null ) { + if ( other.gn != null ) + return false; + } else if ( !gn.equals(other.gn) ) + return false; + if ( prefix == null ) { + if ( other.prefix != null ) + return false; + } else if ( !prefix.equals(other.prefix) ) + return false; + if ( uriStr == null ) { + if ( other.uriStr != null ) + return false; + } else if ( !uriStr.equals(other.uriStr) ) + return false; + return true; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/AddQuad.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/AddQuad.java new file mode 100644 index 00000000000..65fbe24f7e1 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/AddQuad.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +import org.apache.jena.graph.Node; + +public class AddQuad extends ChangeItem { + public final Node g; + public final Node s; + public final Node p; + public final Node o; + + public AddQuad(Node g, Node s, Node p, Node o) { + this.g = g; + this.s = s; + this.p = p; + this.o = o; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((g == null) ? 0 : g.hashCode()); + result = prime * result + ((o == null) ? 0 : o.hashCode()); + result = prime * result + ((p == null) ? 0 : p.hashCode()); + result = prime * result + ((s == null) ? 0 : s.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + AddQuad other = (AddQuad)obj; + if ( g == null ) { + if ( other.g != null ) + return false; + } else if ( !g.equals(other.g) ) + return false; + if ( o == null ) { + if ( other.o != null ) + return false; + } else if ( !o.equals(other.o) ) + return false; + if ( p == null ) { + if ( other.p != null ) + return false; + } else if ( !p.equals(other.p) ) + return false; + if ( s == null ) { + if ( other.s != null ) + return false; + } else if ( !s.equals(other.s) ) + return false; + return true; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/ChangeItem.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/ChangeItem.java new file mode 100644 index 00000000000..502406bf2bd --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/ChangeItem.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +public abstract class ChangeItem { + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object other); +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/DeletePrefix.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/DeletePrefix.java new file mode 100644 index 00000000000..c10cda561c8 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/DeletePrefix.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +import org.apache.jena.graph.Node; + +public class DeletePrefix extends ChangeItem { + public final Node gn; + public final String prefix; + + public DeletePrefix(Node gn, String prefix) { + this.gn = gn; + this.prefix = prefix; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((gn == null) ? 0 : gn.hashCode()); + result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + DeletePrefix other = (DeletePrefix)obj; + if ( gn == null ) { + if ( other.gn != null ) + return false; + } else if ( !gn.equals(other.gn) ) + return false; + if ( prefix == null ) { + if ( other.prefix != null ) + return false; + } else if ( !prefix.equals(other.prefix) ) + return false; + return true; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/DeleteQuad.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/DeleteQuad.java new file mode 100644 index 00000000000..e295368565e --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/DeleteQuad.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +import org.apache.jena.graph.Node; + +public class DeleteQuad extends ChangeItem { + public final Node g; + public final Node s; + public final Node p; + public final Node o; + + public DeleteQuad(Node g, Node s, Node p, Node o) { + this.g = g; + this.s = s; + this.p = p; + this.o = o; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((g == null) ? 0 : g.hashCode()); + result = prime * result + ((o == null) ? 0 : o.hashCode()); + result = prime * result + ((p == null) ? 0 : p.hashCode()); + result = prime * result + ((s == null) ? 0 : s.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + DeleteQuad other = (DeleteQuad)obj; + if ( g == null ) { + if ( other.g != null ) + return false; + } else if ( !g.equals(other.g) ) + return false; + if ( o == null ) { + if ( other.o != null ) + return false; + } else if ( !o.equals(other.o) ) + return false; + if ( p == null ) { + if ( other.p != null ) + return false; + } else if ( !p.equals(other.p) ) + return false; + if ( s == null ) { + if ( other.s != null ) + return false; + } else if ( !s.equals(other.s) ) + return false; + return true; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/HeaderItem.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/HeaderItem.java new file mode 100644 index 00000000000..0ccaa1b50dc --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/HeaderItem.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +import org.apache.jena.graph.Node; + +public class HeaderItem extends ChangeItem { + + public final String field; + public final Node value; + + public HeaderItem(String field, Node value) { + this.field = field; + this.value = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + HeaderItem other = (HeaderItem)obj; + if ( field == null ) { + if ( other.field != null ) + return false; + } else if ( !field.equals(other.field) ) + return false; + if ( value == null ) { + if ( other.value != null ) + return false; + } else if ( !value.equals(other.value) ) + return false; + return true; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/Segment.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/Segment.java new file mode 100644 index 00000000000..6cf3dff20c6 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/Segment.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +public class Segment extends ChangeItem { + @Override + public int hashCode() { + return 20; + } + + @Override + public boolean equals(Object other) { + return other instanceof Segment; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/SetBase.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/SetBase.java new file mode 100644 index 00000000000..8db3ab7905e --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/SetBase.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +public class SetBase extends ChangeItem { + public final String uriStr; + + public SetBase(String uriStr) { + this.uriStr = uriStr; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((uriStr == null) ? 0 : uriStr.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + SetBase other = (SetBase)obj; + if ( uriStr == null ) { + if ( other.uriStr != null ) + return false; + } else if ( !uriStr.equals(other.uriStr) ) + return false; + return true; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnAbort.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnAbort.java new file mode 100644 index 00000000000..8f123b4548b --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnAbort.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +public class TxnAbort extends ChangeItem { + + private static final TxnAbort singleton = new TxnAbort(); + public static TxnAbort object() { + if ( singleton == null ) + // Harmless to have duplicate. + return new TxnAbort(); + return singleton; + } + + @Override + public int hashCode() { + return 16; + } + + @Override + public boolean equals(Object other) { + return other instanceof TxnAbort; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnBegin.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnBegin.java new file mode 100644 index 00000000000..9b72bb5a751 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnBegin.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +public class TxnBegin extends ChangeItem { + + private static final TxnBegin singleton = new TxnBegin(); + public static TxnBegin object() { + if ( singleton == null ) + // Harmless to have duplicate. + return new TxnBegin(); + return singleton; + } + + public TxnBegin() {} + + @Override + public int hashCode() { + return 15; + } + + @Override + public boolean equals(Object other) { + return other instanceof TxnBegin; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnCommit.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnCommit.java new file mode 100644 index 00000000000..e969005fbde --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/items/TxnCommit.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.items; + +public class TxnCommit extends ChangeItem { + + private static final TxnCommit singleton = new TxnCommit(); + public static TxnCommit object() { + if ( singleton == null ) + // Harmless to have duplicate. + return new TxnCommit(); + return singleton; + } + + @Override + public int hashCode() { + return 17; + } + + @Override + public boolean equals(Object other) { + return other instanceof TxnCommit; + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/AbstractDatasetGraphAddDelete.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/AbstractDatasetGraphAddDelete.java new file mode 100644 index 00000000000..58000103020 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/AbstractDatasetGraphAddDelete.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import java.util.Iterator; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphWrapper; +import org.apache.jena.sparql.core.GraphView; +import org.apache.jena.sparql.core.Quad; + +/** Reduce all changes to calls to {@link #actionAdd} and {@link #actionDelete} */ +public abstract class AbstractDatasetGraphAddDelete extends DatasetGraphWrapper { + + public AbstractDatasetGraphAddDelete(DatasetGraph dsg) { + super(dsg) ; + } + + protected abstract void actionAdd(Node g, Node s, Node p, Node o); + protected abstract void actionDelete(Node g, Node s, Node p, Node o); + + @Override + public void add(Quad quad) { + add(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject()); + } + + @Override + public void delete(Quad quad) { + delete(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject()); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + actionAdd(g,s,p,o); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + actionDelete(g, s, p, o); + } + + //Is this needed? + @Override + public void addGraph(Node graphName, Graph graph) { + graph.find(null, null, null) + .forEachRemaining((t) -> this.add(graphName, t.getSubject(), t.getPredicate(), t.getObject())); + } + + @Override + public void removeGraph(Node graphName) + { deleteAny(graphName, Node.ANY, Node.ANY, Node.ANY) ; } + + @Override + public void setDefaultGraph(Graph graph) { + graph.find(null, null, null) + .forEachRemaining((t) -> this.add(Quad.defaultGraphNodeGenerated, t.getSubject(), t.getPredicate(), t.getObject())); + } + + @Override + public void clear() + { deleteAny(Node.ANY, Node.ANY, Node.ANY, Node.ANY) ; } + + @Override + public Graph getDefaultGraph() + { return GraphView.createDefaultGraph(this) ; } + + @Override + public Graph getGraph(Node graphNode) + { return GraphView.createNamedGraph(get(), graphNode) ; } + + // Unbundle deleteAny + private static final int DeleteBufferSize = 10000; + @Override + /** Simple implementation but done without assuming iterator.remove() */ + public void deleteAny(Node g, Node s, Node p, Node o) { + // TODO DRY This code. + // This is duplicated: see DatasetGraphBase. + // We need to do the conversion here. + // DRY => DSGUtils + // Convert deleteAny to deletes. + Quad[] buffer = new Quad[DeleteBufferSize]; + while (true) { + Iterator iter = find(g, s, p, o); + // Get a slice + int len = 0; + for ( ; len < DeleteBufferSize ; len++ ) { + if ( !iter.hasNext() ) + break; + buffer[len] = iter.next(); + } + // Delete them. + for ( int i = 0 ; i < len ; i++ ) { + delete(buffer[i]); + buffer[i] = null; + } + // Finished? + if ( len < DeleteBufferSize ) + break; + } + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/DatasetGraphChanges.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/DatasetGraphChanges.java new file mode 100644 index 00000000000..00bdc776908 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/DatasetGraphChanges.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import java.util.Iterator; +import java.util.function.Consumer; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.query.ReadWrite; +import org.apache.jena.query.TxnType; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.sparql.JenaTransactionException; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphWrapper; +import org.apache.jena.sparql.core.Quad; + +/** + * Connect a {@link DatasetGraph} with {@link RDFChanges}. All operations on the + * {@link DatasetGraph} that cause changes have the change sent to the + * {@link RDFChanges}. + *

+ * Optionally, a sync handler can be given that is called on {@code sync()} or {@code begin}. + * This class is stateless so updating the wrapped dataset is possible via the sync handler. + *

+ * Synchronization can also be performed externally on the wrapped dataset. + *

+ * Use {@link DatasetGraphRealChanges} to get a dataset that logs only changes that have a + * real effect - that makes the changes log reversible (play delete for each add) to undo + * a sequence of changes. + * + * @see DatasetGraphRealChanges + * @see RDFChanges + */ +public class DatasetGraphChanges extends DatasetGraphWrapper { + // Break up? + // inherits DatasetGraphRealChanges < DatasetGraphAddDelete + + protected final Runnable syncHandler; + protected final Consumer txnSyncHandler; + protected final RDFChanges changesMonitor; + private static Runnable identityRunnable = ()->{}; + private static Consumer identityConsumer() { return (x)->{}; } + + /** Create a {@code DatasetGraphChanges} which does not have any sync handlers */ + public DatasetGraphChanges(DatasetGraph dsg, RDFChanges monitor) { + this(dsg, monitor, identityRunnable, identityConsumer()); + } + + /** Create a {@code DatasetGraphChanges} which calls different patch log synchronization + * handlers on {@link #sync} and {@link #begin}. + * {@code syncHandler} defaults (with null) to "no action". + * + * Transactional usage preferred. + */ + public DatasetGraphChanges(DatasetGraph dsg, RDFChanges changesMonitor, Runnable syncHandler, Consumer txnSyncHandler) { + super(dsg); + this.changesMonitor = changesMonitor; + this.syncHandler = syncHandler == null ? identityRunnable : syncHandler; + this.txnSyncHandler = txnSyncHandler == null ? identityConsumer() : txnSyncHandler; + } + + public RDFChanges getMonitor() { return changesMonitor; } + + @Override public void sync() { + syncHandler.run(); + if ( syncHandler != identityRunnable ) + super.sync(); + } + + @Override + public void add(Quad quad) { + add(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject()); + } + + @Override + public void delete(Quad quad) { + delete(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject()); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + requireWriteTxn(); + changesMonitor.add(g, s, p, o); + super.add(g, s, p, o); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + requireWriteTxn(); + changesMonitor.delete(g, s, p, o); + super.delete(g, s, p, o); + } + + private void requireWriteTxn() { + ReadWrite mode = transactionMode(); + if ( mode == ReadWrite.WRITE ) + return; + boolean b = promote() ; + if ( !b ) + throw new JenaTransactionException("Can't write") ; + } + + @Override + public Graph getDefaultGraph() + { return new GraphChanges(get().getDefaultGraph(), null, changesMonitor) ; } + + @Override + public Graph getGraph(Node graphNode) + { return new GraphChanges(get().getGraph(graphNode), graphNode, changesMonitor) ; } + + @Override + public void addGraph(Node graphName, Graph data) { + removeGraph(graphName); + data.find().forEachRemaining((t) -> add(graphName, t.getSubject(), t.getPredicate(), t.getObject())); + } + + @Override + public void removeGraph(Node graphName) { + deleteAny(graphName, Node.ANY, Node.ANY, Node.ANY); + } + + private static final int DeleteBufferSize = 10000; + @Override + /** Simple implementation but done without assuming iterator.remove() */ + public void deleteAny(Node g, Node s, Node p, Node o) { + requireWriteTxn(); + Quad[] buffer = new Quad[DeleteBufferSize]; + while (true) { + Iterator iter = find(g, s, p, o); + // Get a slice + int len = 0; + for ( ; len < DeleteBufferSize ; len++ ) { + if ( !iter.hasNext() ) + break; + buffer[len] = iter.next(); + } + // Delete them. + for ( int i = 0 ; i < len ; i++ ) { + delete(buffer[i]); + buffer[i] = null; + } + // Finished? + if ( len < DeleteBufferSize ) + break; + } + } + + // In case an implementation has one "begin" calling another. + private ThreadLocal insideBegin = ThreadLocal.withInitial(()->false); + + @Override + public void begin() { + if ( insideBegin.get() ) { + super.begin(); + return; + } + insideBegin.set(true); + try { + ReadWrite readWrite = transactionMode(); + // If a write, start the changedMonitor including get the patch log lock. + if ( readWrite == ReadWrite.WRITE ) + changesMonitor.txnBegin(); + // For the sync, we have to assume it will write. + // Any transaction causes a write-sync to be done in "begin". + txnSyncHandler.accept(readWrite); + super.begin(); + } finally { + insideBegin.set(false); + } + internalBegin(); + } + + /** Called after begin and sync has occurred. */ + protected void internalBegin() {} + + @Override + public void begin(TxnType txnType) { + if ( insideBegin.get() ) { + super.begin(txnType); + return; + } + insideBegin.set(true); + try { + if ( txnType == TxnType.WRITE) + changesMonitor.txnBegin(); + // For the sync, we have to assume it will write. + // Sync: do as if a WRITE if the transaction is a "promote" + // There isn't a sync during promotion. + if ( txnType != TxnType.READ ) + txnSyncHandler.accept(ReadWrite.WRITE); + else + txnSyncHandler.accept(ReadWrite.READ); + super.begin(txnType); + } finally { + insideBegin.set(false); + } + internalBegin(); + } + + @Override + public void begin(ReadWrite readWrite) { + TxnType txnType = TxnType.convert(readWrite); + begin(txnType); + } + + @Override + public boolean promote() { + // Do not use the wrapper code which will redirect to the wrapped DSG + // bypassing promote(Promote) below. + // This is copied :-( from "Transactional" + TxnType txnType = transactionType(); + if ( txnType == null ) + throw new JenaTransactionException("Not in a transaction") ; + switch(txnType) { + case WRITE : return true; + case READ : return false; + case READ_PROMOTE : return promote(Promote.ISOLATED); + case READ_COMMITTED_PROMOTE : return promote(Promote.READ_COMMITTED); + } + throw new JenaTransactionException("Can't determine promote '"+txnType+"'transaction"); + } + + @Override + public boolean promote(Promote type) { + // Any potential write causes a write-sync to be done in "begin". + // Here we are inside the transaction so calling the sync handler is not possible (nested transaction risk). + if ( super.transactionMode() == ReadWrite.READ ) { + boolean b = super.promote(type); + if ( super.transactionMode() == ReadWrite.WRITE ) { + // Promotion happened. + // READ_PROMOTE would not reveal any new triples. + // READ_COMMITTED_PROMOTE : can't atomically do local and remote "begin(write)" + // Nested transaction. See above. +// if ( transactionType() == TxnType.READ_COMMITTED_PROMOTE ) +// txnSyncHandler.accept(ReadWrite.WRITE); + // We have gone ReadWrite.READ -> ReadWrite.WRITE + changesMonitor.txnBegin(); + } + return b; + } + //Already WRITE. + return super.promote(type); + } + + @Override + public void commit() { + // Assume local commit will work - so signal first. + // If the monitor.txnCommit fails, the commit should not happen + if ( isWriteMode() ) { + try { + changesMonitor.txnCommit(); + } catch (Exception ex) { + //Don't signal. monitor.txnAbort() is a client-caused abort. + super.abort(); + throw ex; + //return; + } + } + super.commit(); + } + + @Override + public void abort() { + // Assume abort will work - signal first. + if ( isWriteMode() ) + changesMonitor.txnAbort(); + super.abort(); + } + + private boolean isWriteMode() { + return super.transactionMode() == ReadWrite.WRITE; + } + +// @Override +// public void end() { +// super.end(); +// } + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/DatasetGraphRealChanges.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/DatasetGraphRealChanges.java new file mode 100644 index 00000000000..31e2e2f6085 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/DatasetGraphRealChanges.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.sparql.core.DatasetGraph; + +// UNFINISHED + +// ?? Prefixes. +public class DatasetGraphRealChanges extends AbstractDatasetGraphAddDelete { + /** With checking, the {@link RDFChanges} becomes reversible */ + protected final boolean checking = true ; + + public DatasetGraphRealChanges(DatasetGraph dsg) { + super(dsg) ; + } + + @Override + protected void actionAdd(Node g, Node s, Node p, Node o) { + if ( checking && get().contains(g, s, p, o) ) + return; + get().add(g, s, p, o); + } + + @Override + protected void actionDelete(Node g, Node s, Node p, Node o) { + if ( checking && ! get().contains(g, s, p, o) ) + return; + get().delete(g, s, p, o); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/GraphChanges.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/GraphChanges.java new file mode 100644 index 00000000000..e55f12e1d06 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/GraphChanges.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.*; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.graph.GraphWrapper; + +/** + * Connect a {@link Graph} with {@link RDFChanges}. All operations on the {@link Graph} + * that cause changes have the change sent to the {@link RDFChanges}. + *

+ * Use {@link GraphRealChanges} to get a graph that logs only changes that have a real + * effect - that makes the changes log reversible (play delete for each add) to undo a + * sequence of changes. + *

+ * The graph name is settable; it is arbitrary, or null, and not deduced from the graph. + *

+ * If the graph is a graph from a dataset and the same graph name is to be used, then + * instead of this class, get a graph from {@link DatasetGraphChanges}. + * + * @see DatasetGraphChanges + * @see RDFChanges + */ +public class GraphChanges extends GraphWrapper /*implements GraphWithPerform*/ // Or WrappedGraph +{ + private final RDFChanges changes; + protected final Node graphName; + private final PrefixMapping prefixMapping; + private final TransactionHandler transactionHandler; + + /** + * Send changes to a graph to a {@link RDFChanges} with null for the graph name. + */ + public GraphChanges(Graph graph, RDFChanges changes) { + this(graph, null, changes); + } + + /** + * Send changes to a graph to a {@link RDFChanges} with the specified graph name. + * The graph name may be null for "no name". + */ + public GraphChanges(Graph graph, Node graphName, RDFChanges changes) { + super(graph); + this.graphName = graphName; + this.changes = changes; + this.prefixMapping = new PrefixMappingChanges(graph, graphName, changes); + this.transactionHandler = new TransactionHandlerMonitor(graph.getTransactionHandler(), changes); + } + + @Override + public void add(Triple t) { + changes.add(graphName, t.getSubject(), t.getPredicate(), t.getObject()); + super.add(t); + } + + @Override + public void delete(Triple t) { + changes.delete(graphName, t.getSubject(), t.getPredicate(), t.getObject()); + super.delete(t); + } + +// @Override public void performAdd( Triple t ) { add(t); } +// @Override public void performDelete( Triple t ) { delete(t); } + + @Override + public void clear() { + remove(Node.ANY, Node.ANY, Node.ANY); + } + + @Override + public PrefixMapping getPrefixMapping() { + return prefixMapping; + } + + @Override + public void remove(Node s, Node p, Node o) { + // Convert to calls to delete. + GraphUtil.remove(this, s, p, o); + } + + @Override + public TransactionHandler getTransactionHandler() { + return transactionHandler; + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/GraphRealChanges.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/GraphRealChanges.java new file mode 100644 index 00000000000..023d95b84f2 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/GraphRealChanges.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.rdfpatch.RDFChanges; + +/** + * A {@link Graph} and {@link RDFChanges} that check whether a triple change is real or + * not and only passes the chnage on if the add(triple)/delete(triple) causes an actual + * change to the graph. + * + * @see GraphChanges + */ +public class GraphRealChanges extends GraphChanges +{ + public GraphRealChanges(Graph graph, RDFChanges changes) { + super(graph, changes); + } + + public GraphRealChanges(Graph graph, Node graphName, RDFChanges changes) { + super(graph, graphName, changes); + } + + @Override + public void add(Triple t) { + if ( ! super.contains(t) ) + super.add(t); + } + + @Override + public void delete(Triple t) { + if ( super.contains(t) ) + super.delete(t); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/InitPatch.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/InitPatch.java new file mode 100644 index 00000000000..871104c8c75 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/InitPatch.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.sys.JenaSubsystemLifecycle; +import org.apache.jena.sys.JenaSystem; + +/** Patch initialization using Jena system initialization + *

+ * Jena system initialization + */ +public class InitPatch implements JenaSubsystemLifecycle { + public static int level = 60; + + @Override + public void start() { + JenaSystem.logLifecycle("Patch.init - start"); + PatchSystem.init(); + JenaSystem.logLifecycle("Patch.init - finish"); + } + + @Override + public void stop() {} + + @Override + public int level() { return level ; } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PatchSystem.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PatchSystem.java new file mode 100644 index 00000000000..ebd8749e0db --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PatchSystem.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.rdfpatch.filelog.VocabPatch; +import org.apache.jena.sys.JenaSystem; + +public class PatchSystem { + + /** This is automatically called by the Jena subsystem startup cycle. + * See {@link InitPatch} and {@code META_INF/services/org.apache.jena.system.JenaSubsystemLifecycle} + */ + public static void init( ) { init$(); } + + private static Object initLock = new Object(); + private static volatile boolean initialized = false; + + private static void init$() { + if ( initialized ) + return; + synchronized(initLock) { + if ( initialized ) { + JenaSystem.logLifecycle("Patch.init - return"); + return; + } + initialized = true; + JenaSystem.logLifecycle("Patch.init - start"); + VocabPatch.init(); + JenaSystem.logLifecycle("Patch.init - finish"); + } + } + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PrefixMappingChanges.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PrefixMappingChanges.java new file mode 100644 index 00000000000..923a28c12f7 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PrefixMappingChanges.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.shared.PrefixMapping; + +/** + * A monitor that sends change operations to an {@link RDFChanges}. + */ +public class PrefixMappingChanges extends PrefixMappingMonitor { + private final RDFChanges changes; + protected final Node graphName; + private Graph graph; + + public PrefixMappingChanges(Graph graph, Node graphName, RDFChanges changes) { + super(null); + this.graph = graph; + this.graphName = graphName; + this.changes = changes; + } + + // Delay getting prefix mapping until now. + // e.g. Graph internals may change across transaction boundaries. + @Override + protected PrefixMapping get() { return graph.getPrefixMapping() ; } + + @Override + protected void set(String prefix, String uri) { + changes.addPrefix(graphName, prefix, uri); + } + + @Override + protected void remove(String prefix) { + changes.deletePrefix(graphName, prefix); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PrefixMappingMonitor.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PrefixMappingMonitor.java new file mode 100644 index 00000000000..9fb7105b7d7 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/PrefixMappingMonitor.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import java.util.Map; + +import org.apache.jena.shared.PrefixMapping; + +/** + * An implementation of {@link PrefixMapping} that is a wrapper for another PrefixMapping + * and also decomposes all changes to set/remove operations. + */ +public class PrefixMappingMonitor implements PrefixMapping { + + private final PrefixMapping pmap; + + public PrefixMappingMonitor(PrefixMapping pmap) { this.pmap = pmap ; } + + protected PrefixMapping get() { return pmap ; } + + // The two change operations + protected void set(String prefix, String uri) { } + +// protected String get(String prefix) { } + + protected void remove(String prefix) { } + + @Override + public PrefixMapping setNsPrefix(String prefix, String uri) { + set(prefix, uri); + get().setNsPrefix(prefix, uri); + return this; + } + + @Override + public PrefixMapping removeNsPrefix(String prefix) { + remove(prefix); + get().removeNsPrefix(prefix); + return this; + } + + @Override + public PrefixMapping clearNsPrefixMap() { + get().getNsPrefixMap().forEach((prefix,uri)->remove(prefix)); + get().clearNsPrefixMap(); + return this; + } + + @Override + public PrefixMapping setNsPrefixes(PrefixMapping other) { + other.getNsPrefixMap().forEach((p,u) -> set(p,u)); + get().setNsPrefixes(other); + return this; + } + + @Override + public PrefixMapping setNsPrefixes(Map map) { + map.forEach((p,u) -> set(p,u)); + get().setNsPrefixes(map); + return this; + } + + @Override + public PrefixMapping withDefaultMappings(PrefixMapping map) { + // fake + map.getNsPrefixMap().forEach((p,u) -> set(p,u)); + get().withDefaultMappings(map); + return this; + } + + @Override + public String getNsPrefixURI(String prefix) { + return get().getNsPrefixURI(prefix); + } + + @Override + public String getNsURIPrefix(String uri) { + return get().getNsURIPrefix(uri); + } + + @Override + public Map getNsPrefixMap() { + return get().getNsPrefixMap(); + } + + @Override + public String expandPrefix(String prefixed) { + return get().expandPrefix(prefixed); + } + + @Override + public String shortForm(String uri) { + return get().shortForm(uri); + } + + @Override + public String qnameFor(String uri) { + return get().qnameFor(uri); + } + + @Override + public PrefixMapping lock() { + get().lock(); + return this; + } + + @Override + public boolean hasNoMappings() { + return get().hasNoMappings(); + } + + @Override + public int numPrefixes() { + return get().numPrefixes(); + } + + @Override + public boolean samePrefixMappingAs(PrefixMapping other) { + return get().samePrefixMappingAs(other); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/Printer.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/Printer.java new file mode 100644 index 00000000000..74557736a40 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/Printer.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +@FunctionalInterface +public interface Printer { + void print(String fmt, Object... args); +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/RDFChangesSuppressEmpty.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/RDFChangesSuppressEmpty.java new file mode 100644 index 00000000000..faf75eeb529 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/RDFChangesSuppressEmpty.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.changes.RDFChangesWrapper; + +/** Note whether a change has happened and call different operations on txnCommit + * A change is a call to one of the dataset-changing operations, + * {@code add}, {@code delete}, {@code addPrefix}, {@code deletePrefix}. + * Setting headers is not consided a change. + *

+ * If a change has been made, {@link #txnChangeCommit()} is called, otherwise + * {@link #txnNoChangeCommit()} is called. The implementation of these methods must call + * {@link #doCommit()}, to accept the change or {@link #doAbort()} to cancel it. + *

+ * A common case is to suppress empty transactions. + * To do this, call {@link #doAbort()} in {@link #txnNoChangeCommit()} + */ +public class RDFChangesSuppressEmpty extends RDFChangesWrapper { + // TODO Add RDFChanges.cancel. + + public RDFChangesSuppressEmpty(RDFChanges other) { + super(other); + } + + private boolean changeHappened = false; + protected boolean changeHappened() { return changeHappened; } + + private void markChanged() { + changeHappened = true; + } + +// @Override +// public void start() {} +// +// @Override +// public void finish() {} + + // Headers do not count as "changes". + // A patch must have certain headers for use with a patch log - an id, and a prev. + @Override + public void header(String field, Node value) { + super.header(field, value); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + markChanged(); + super.add(g, s, p, o); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + markChanged(); + super.delete(g, s, p, o); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + markChanged(); + super.addPrefix(gn, prefix, uriStr); + } + + @Override + public final void deletePrefix(Node gn, String prefix) { + markChanged(); + super.deletePrefix(gn, prefix); + } + + @Override + public final void txnBegin() { + changeHappened = false; + super.txnBegin(); + } + + @Override + public final void txnCommit() { + if ( changeHappened ) + txnChangeCommit(); + else + txnNoChangeCommit(); + changeHappened = false; + } + + @Override + public final void txnAbort() { + txnAborting(); + changeHappened = false; + } + + /** + * Called when commit called and there were no changes made (no calls to add/delete + * operations of quads or prefixes). + *

+ * The default implementation is to call + * {@link #doAbort()}. + *

+ * If this is a call to {@link #doCommit()} + * then the process is a no-op. + */ + protected void txnNoChangeCommit() { + doAbort(); + } + + /** + * Called when commit called and there were were changes made. + * The default implementation is to call {@link #doCommit()}. + */ + protected void txnChangeCommit() { + doCommit(); + } + + /** Called when abort called. */ + protected void txnAborting() { + doAbort(); + } + + // Let subclasses call the wrapped txnCommit() + protected void doCommit() { + super.txnCommit(); + } + + // Let subclasses call the wrapped txnAbort() + protected void doAbort() { + super.txnAbort(); + } + +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/RDFPatchAltHeader.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/RDFPatchAltHeader.java new file mode 100644 index 00000000000..86acd7f53e8 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/RDFPatchAltHeader.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.PatchHeader; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.RDFPatch; +import org.apache.jena.rdfpatch.changes.RDFChangesWrapper; + +/** + * An {@link RDFPatch} where the header is taken from one place and the body + * (everything that isn't header) from an existing patch. + */ +public class RDFPatchAltHeader implements RDFPatch { + + private final RDFPatch body; + private final PatchHeader header; + + public RDFPatchAltHeader(PatchHeader header, RDFPatch body) { + this.body = body; + this.header = header; + } + + @Override + public PatchHeader header() { + return header; + } + + @Override + public void apply(RDFChanges changes) { + // Ignore the header. + RDFChanges x = new RDFChangesWrapper(changes) { + @Override public void header(String field, Node value) { } + }; + header.apply(changes); + body.apply(x); + } + + @Override + public boolean repeatable() { + return body.repeatable(); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/TransactionHandlerMonitor.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/TransactionHandlerMonitor.java new file mode 100644 index 00000000000..27b16409a1d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/TransactionHandlerMonitor.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.graph.TransactionHandler; +import org.apache.jena.graph.impl.TransactionHandlerBase; +import org.apache.jena.rdfpatch.RDFChanges; + +/** A graph {@link TransactionHandler} that provides monitoring callbacks. */ +class TransactionHandlerMonitor extends TransactionHandlerBase { + + private final TransactionHandler handler; + private final RDFChanges changes; + + public TransactionHandlerMonitor(TransactionHandler handler, RDFChanges changes) { + this.handler = handler; + this.changes = changes; + } + + @Override + public boolean transactionsSupported() { + return handler.transactionsSupported(); + } + + @Override + public void begin() { + changes.txnBegin(); + handler.begin(); + } + + @Override + public void commit() { + // Must be in this order - log the commit, then do it. + // Recovery from the log wil replay the changes do the log is more important + // than the graph as the source of truth. + handler.commit(); + changes.txnCommit(); + } + + @Override + public void abort() { + // Must be in this order - record the abort then do it + // (a crash between will cause an abort). + changes.txnAbort(); + handler.abort(); + } +} \ No newline at end of file diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/TxnSyncHandler.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/TxnSyncHandler.java new file mode 100644 index 00000000000..90a46747a2d --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/TxnSyncHandler.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import org.apache.jena.query.TxnType; +public interface TxnSyncHandler { + void onBegin(TxnType txnType); + void onPromote(); +// void onCommit(); +// void onAbort(); +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/URNs.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/URNs.java new file mode 100644 index 00000000000..570748841c0 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/system/URNs.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.system; + +import java.util.UUID; + +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; + +/** + * Some functions to do with URN {@link Node Nodes}. + * All UUID generation should go though this class. + */ +public class URNs { + public static final String SchemeUuid = "uuid:"; + public static final String SchemeUrnUuid = "urn:uuid:"; + + private static final String SCHEME = SchemeUuid; + // Version 1 are guessable. + // Version 4 are not. + + /** Generate a UUID */ + public static UUID genUUID() { return UUID.randomUUID() ; } + + /** + * This is not a function! + * It returns a fresh UUID URI on every call. + */ + public static Node unique() { + return unique(SCHEME); + } + + /** + * This is not a function! + * It returns a fresh UUID URI with the given scheme name on every call. + */ + public static Node unique(String schemeName) { + return NodeFactory.createURI(schemeName+genUUID().toString()); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/RDFChangesWriterText.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/RDFChangesWriterText.java new file mode 100644 index 00000000000..28063fed298 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/RDFChangesWriterText.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.text; + +import static org.apache.jena.rdfpatch.changes.PatchCodes.*; + +import java.io.OutputStream; + +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.sparql.core.Quad; + +/** + * Write out a changes as a stream of syntax tokens. + */ +public class RDFChangesWriterText implements RDFChanges, AutoCloseable { + protected TokenWriter tok; + + /** Create a {@code RDFChangesWriter} with standard text output. */ + public static RDFChangesWriterText create(OutputStream out) { + return new RDFChangesWriterText(TokenWriterText.create(out)); + } + + public RDFChangesWriterText(TokenWriter out) { + this.tok = out; + } + + @Override + public void start() { } + + @Override + public void finish() { tok.flush(); } + + @Override + public void header(String field, Node value) { + tok.startTuple(); + tok.sendWord("H"); + tok.sendWord(field); + output(value); + tok.endTuple(); + } + + @Override + public void close() { + tok.close(); + } + + @Override + public void add(Node g, Node s, Node p, Node o) { + output(ADD_DATA, g, s, p, o); + } + + private void output(String code, Node g, Node s, Node p, Node o) { + tok.startTuple(); + outputWord(code); + output(s); + output(p); + output(o); + if ( g != null && ! Quad.isDefaultGraph(g) ) + output(g); + tok.endTuple(); + } + + private void output(Node node) { + tok.sendNode(node); + } + + private void outputWord(String code) { + tok.sendWord(code); + } + + @Override + public void delete(Node g, Node s, Node p, Node o) { + output(DEL_DATA, g, s, p, o); + } + + @Override + public void addPrefix(Node gn, String prefix, String uriStr) { + tok.startTuple(); + outputWord(ADD_PREFIX); + tok.sendString(prefix); + tok.sendString(uriStr); + if ( gn != null ) + tok.sendNode(gn); + tok.endTuple(); + } + + @Override + public void deletePrefix(Node gn, String prefix) { + tok.startTuple(); + outputWord(DEL_PREFIX); + tok.sendString(prefix); + if ( gn != null ) + tok.sendNode(gn); + tok.endTuple(); + } + + private void oneline(String code) { + tok.startTuple(); + tok.sendWord(code); + tok.endTuple(); + } + + @Override + public void txnBegin() { + oneline(TXN_BEGIN); + } + + @Override + public void txnCommit() { + oneline(TXN_COMMIT); + tok.flush(); + } + + @Override + public void txnAbort() { + oneline(TXN_ABORT); + tok.flush(); + } + + @Override + public void segment() { + oneline(SEGMENT); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/RDFPatchReaderText.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/RDFPatchReaderText.java new file mode 100644 index 00000000000..1beaa526071 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/RDFPatchReaderText.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.text; + +import static org.apache.jena.rdfpatch.changes.PatchCodes.*; +import static org.apache.jena.riot.tokens.TokenType.DOT; +import static org.apache.jena.riot.tokens.TokenType.GT2; +import static org.apache.jena.riot.tokens.TokenType.LT2; + +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.rdfpatch.PatchException; +import org.apache.jena.rdfpatch.PatchHeader; +import org.apache.jena.rdfpatch.PatchProcessor; +import org.apache.jena.rdfpatch.RDFChanges; +import org.apache.jena.rdfpatch.changes.PatchCodes; +import org.apache.jena.riot.SysRIOT; +import org.apache.jena.riot.system.ErrorHandler; +import org.apache.jena.riot.system.ErrorHandlerFactory; +import org.apache.jena.riot.system.RiotLib; +import org.apache.jena.riot.tokens.Token; +import org.apache.jena.riot.tokens.TokenType; +import org.apache.jena.riot.tokens.Tokenizer; +import org.apache.jena.riot.tokens.TokenizerText; + +/** RDF Patch reader for text format. */ +public class RDFPatchReaderText implements PatchProcessor { + private final Tokenizer tokenizer; + + // Return true on end of transaction. + private static void read(Tokenizer tokenizer, RDFChanges changes) { + while( tokenizer.hasNext() ) { + apply1(tokenizer, changes); + } + } + + public RDFPatchReaderText(InputStream input) { + this(input, ErrorHandlerFactory.errorHandlerExceptionOnError()); + } + + public RDFPatchReaderText(InputStream input, ErrorHandler errorHandler) { + tokenizer = TokenizerText.create().source(input).errorHandler(errorHandler).build(); + } + + @Override + public void apply(RDFChanges processor) { + read(tokenizer, processor); + } + + /** + * Execute one tuple, skipping blanks and comments. + * Return true if there is the possibility of more. + */ + private static boolean apply1(Tokenizer input, RDFChanges sink) { + boolean oneTransaction = true; + long lineNumber = 0; + while(input.hasNext()) { + try { + lineNumber++; + boolean b = doOneLine(input, sink); + if ( oneTransaction && b ) + return true; + } catch (Exception ex) { + sink.txnAbort(); + throw ex; + } + } + return false; + } + + // Return true for "end transaction". + private static boolean doOneLine(Tokenizer tokenizer, RDFChanges sink) { + if ( !tokenizer.hasNext() ) + return false; + Token tokCode = tokenizer.next(); + if ( tokCode.hasType(DOT) ) + throw exception(tokCode, "Empty line"); + if ( ! tokCode.isWord() ) + throw exception(tokCode, "Expected keyword at start of patch record"); + + String code = tokCode.getImage(); + switch (code) { + case HEADER: { + readHeaderLine(tokenizer, (f,v)->sink.header(f, v)); + return false; + } + + case ADD_DATA: { + Node s = nextNode(tokenizer); + Node p = nextNode(tokenizer); + Node o = nextNode(tokenizer); + Node g = nextNodeMaybe(tokenizer); + skip(tokenizer, DOT); + sink.add(g, s, p, o); + return false; + } + case DEL_DATA: { + Node s = nextNode(tokenizer); + Node p = nextNode(tokenizer); + Node o = nextNode(tokenizer); + Node g = nextNodeMaybe(tokenizer); + skip(tokenizer, DOT); + sink.delete(g, s, p, o); + return false; + } + case ADD_PREFIX: { + Token tokPrefix = nextToken(tokenizer); + if ( tokPrefix == null ) + throw exception(tokenizer, "Prefix add tuple too short"); + String prefix = tokPrefix.asString(); + if ( prefix == null ) + throw exception(tokPrefix, "Prefix is not a string: %s", tokPrefix); + String uriStr; + Token tokURI = nextToken(tokenizer); + if ( tokURI.isIRI() ) + uriStr = tokURI.getImage(); + else if ( tokURI.isString() ) + uriStr = tokURI.asString(); + else + throw exception(tokURI, "Prefix error: URI slot is not a URI nor a string"); + Node gn = nextNodeMaybe(tokenizer); + skip(tokenizer, DOT); + sink.addPrefix(gn, prefix, uriStr); + return false; + } + case DEL_PREFIX: { + Token tokPrefix = nextToken(tokenizer); + if ( tokPrefix == null ) + throw exception(tokenizer, "Prefix delete tuple too short"); + String prefix = tokPrefix.asString(); + if ( prefix == null ) + throw exception(tokPrefix, "Prefix is not a string: %s", tokPrefix); + Node gn = nextNodeMaybe(tokenizer); + skip(tokenizer, DOT); + sink.deletePrefix(gn, prefix); + return false; + } + case TXN_BEGIN: + // Alternative name: + case "TB": { + skip(tokenizer, DOT); + sink.txnBegin(); + return false; + } + case TXN_COMMIT: { + skip(tokenizer, DOT); + sink.txnCommit(); + return true; + } + case TXN_ABORT: { + skip(tokenizer, DOT); + sink.txnAbort(); + return true; + } + case SEGMENT: { + skip(tokenizer, DOT); + sink.segment(); + return false; + } + default: { + throw exception(tokenizer, "Code '%s' not recognized", code); + } + } + } + + private final static String bNodeLabelStart = "_:"; + private static Node tokenToNode(Token token) { + if ( token.isIRI() ) + // URI or <_:...> + return RiotLib.createIRIorBNode(token.getImage()); + if ( token.isBNode() ) { + // Blank node as _:... + String label = token.getImage().substring(bNodeLabelStart.length()); + return NodeFactory.createBlankNode(label); + } + Node node = token.asNode(); + if ( node == null ) + throw exception(token, "Expect a Node, got %s",token); + return node; + } + + /** Read patch header. */ + public static PatchHeader readerHeader(InputStream input) { + Tokenizer tokenizer = TokenizerText.create().source(input).build(); + Map header = new LinkedHashMap<>(); + int lineNumber = 0; + while(tokenizer.hasNext()) { + Token tokCode = tokenizer.next(); + if ( tokCode.hasType(DOT) ) + throw exception(tokCode, "Empty header line"); + if ( ! tokCode.isWord() ) + throw exception(tokCode, "Expected keyword at start of patch header"); + String code = tokCode.getImage(); + lineNumber ++; + if ( ! code.equals(PatchCodes.HEADER) ) + break; + readHeaderLine(tokenizer, (f,n)->header.put(f, n)); + } + return new PatchHeader(header); + } + + /** Known-to-be-header line */ + private static void readHeaderLine(Tokenizer tokenizer, BiConsumer action) { + Token token2 = nextToken(tokenizer); + if ( ! token2.isWord() && ! token2.isString() ) + throw exception(tokenizer, "Header does not have a key that is a word: "+token2); + String field = token2.getImage(); + Node v = nextNode(tokenizer); + skip(tokenizer, DOT); + action.accept(field, v); + } + + private static void skip(Tokenizer tokenizer, TokenType tokenType ) { + Token tok = tokenizer.next(); + if ( ! tok.hasType(tokenType) ) + throw exception(tok, "Expected token type: "+tokenType+": got "+tok); + } + + private static Node nextNodeMaybe(Tokenizer tokenizer) { + Token tok = tokenizer.peek(); + if ( tok.hasType(DOT) ) + return null; + if ( tok.isEOF() ) + throw exception(tokenizer, "Input truncated: no DOT seen on last line"); + return tokenToNode(tokenizer.next()); + } + + // Next token, required, must not be EOF or DOT. + private static Token nextToken(Tokenizer tokenizer) { + if ( ! tokenizer.hasNext() ) + throw exception(tokenizer, "Input truncated"); + Token tok = tokenizer.next(); + if ( tok.hasType(DOT) ) + throw exception(tok, "Input truncated by DOT: line too short"); + if ( tok.isEOF() ) + throw exception(tok, "Input truncated: no DOT seen on last line"); + return tok; + } + + private static Node nextNode(Tokenizer tokenizer) { + Token tok = nextToken(tokenizer); + if ( tok.hasType(LT2) ) { + Node s = nextNode(tokenizer); + Node p = nextNode(tokenizer); + Node o = nextNode(tokenizer); + Token tok2 = nextToken(tokenizer); + if ( ! tok2.hasType(GT2) ) + exception(tok2, "Expected token type: "+GT2+": got "+tok2); + return NodeFactory.createTripleNode(s, p, o); + } + return tokenToNode(tok); + } + + private static PatchException exception(Tokenizer tokenizer, String fmt, Object... args) { + String msg = String.format(fmt, args); + if ( tokenizer != null ) + msg = SysRIOT.fmtMessage(msg, tokenizer.getLine(), tokenizer.getColumn()); + return new PatchException(msg); + } + + private static PatchException exception(Token token, String fmt, Object... args) { + String msg = String.format(fmt, args); + if ( token != null ) + msg = SysRIOT.fmtMessage(msg, token.getLine(), token.getColumn()); + return new PatchException(msg); + } +} diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/TokenWriter.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/TokenWriter.java new file mode 100644 index 00000000000..8b7a33d7a05 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/TokenWriter.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.text; + +import org.apache.jena.graph.Node; +import org.apache.jena.riot.tokens.Token; + +public interface TokenWriter { + public void sendToken(Token token); + + // Fast-track common cases + public void sendNode(Node node); + public void sendString(String string); + public void sendWord(String word); + public void sendNumber(long number); + + public void startTuple(); + public void endTuple(); + + public void flush(); + public void close(); + +} + diff --git a/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/TokenWriterText.java b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/TokenWriterText.java new file mode 100644 index 00000000000..c3da282b371 --- /dev/null +++ b/jena-rdfpatch/src/main/java/org/apache/jena/rdfpatch/text/TokenWriterText.java @@ -0,0 +1,424 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.text; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.io.Writer; + +import org.apache.jena.atlas.io.AWriter; +import org.apache.jena.atlas.io.BufferingWriter; +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.lib.Chars; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.RiotException; +import org.apache.jena.riot.out.NodeFormatter; +import org.apache.jena.riot.out.NodeFormatterTTL; +import org.apache.jena.riot.system.PrefixMapFactory; +import org.apache.jena.riot.tokens.Token; +import org.apache.jena.sparql.util.FmtUtils; + +public class TokenWriterText implements TokenWriter { + // Whether to space out the tuples a bit for readability. + private static final boolean GAPS = true; + + private final AWriter out; + private boolean inTuple = false; + private boolean inSection = false; + + private final NodeFormatter fmt; + + private String label; + + public static TokenWriter create(OutputStream out) { + return new TokenWriterText(out); + } + + public static TokenWriter create(StringWriter out) { + return new TokenWriterText(out); + } + + public static TokenWriter create(AWriter out) { + return new TokenWriterText(out); + } + + /** + * Create a TokenOutputStreamWriter going to a OutputStream. + * + * @param out + */ + private TokenWriterText(OutputStream out) { + this(writer(out)); + } + + private static Writer writer(OutputStream out) { + return IO.asUTF8(out); + } + + /** + * Create a TokenOutputStreamWriter going to a StringWriter. + * + * @param out + */ + private TokenWriterText(Writer out) { + this(null, null, IO.wrap(out)); + } + + /** + * Create a TokenOutputStreamWriter going to a Writer, ideally one that + * buffers (e.g. {@linkplain BufferingWriter}). + * + * @param out + */ + private TokenWriterText(AWriter out) { + this(null, null, out); + } + + /** + * Create a TokenOutputStreamWriter going to a Writer, with a given + * NodeFormatter policy ideally one that buffers (e.g. + * {@linkplain BufferingWriter}). + */ + + private TokenWriterText(String label, NodeFormatter formatter, AWriter out) { + //formatter = new NodeFormatterBNode(formatter); + formatter = new NodeFormatterBNode1(); + this.fmt = formatter; + this.out = out; + this.label = label; + } + + // NodeFormatterTTL to get the number abbreviations. + static class NodeFormatterBNode1 extends NodeFormatterTTL { + + public NodeFormatterBNode1() { + super(null, PrefixMapFactory.emptyPrefixMap()); + } + + @Override + public void formatBNode(AWriter w, Node n) { + formatBNode(w, n.getBlankNodeLabel()); + } + + @Override + public void formatBNode(AWriter w, String label) { + w.print("<_:"); + w.print(label); + w.print(">"); + } + } + + // Temporary - for reference. + // See notes about NodeTriple + +// static class NodeFormatterBNode extends NodeFormatterWrapper { +// public NodeFormatterBNode(NodeFormatter other) { +// super(other); +// } +// +// @Override +// public void format(AWriter w, Node n) { +// if ( n.isBlank() ) { +// formatBNode(w, n); +// return; +// } +// // Wrapped does not work with RDF-star because we need to override the bnode format recusively. +// // IF wrapped. +//// if ( n.isNodeTriple() ) { +//// // Need to print blank nodes with this code. +//// Triple t = n.getTriple(); +//// w.print("<< "); +//// format(w, t.getSubject()); +//// w.print(" "); +//// format(w, t.getPredicate()); +//// w.print(" "); +//// format(w, t.getObject()); +//// // Need to write bnodes "our way". +//// w.print(" >>"); +//// return; +//// } +// +// super.format(w, n); +// } +// +// @Override +// public void formatBNode(AWriter w, Node n) { +// formatBNode(w, n.getBlankNodeLabel()); +// } +// +// @Override +// public void formatBNode(AWriter w, String label) { +// w.print("<_:"); +// w.print(label); +// w.print(">"); +// } +// } +// +// static class NodeFormatterWrapper implements NodeFormatter { +// private final NodeFormatter fmt; +// +// public NodeFormatterWrapper(NodeFormatter other) { +// this.fmt = other; +// } +// +// @Override +// public void format(AWriter w, Node n) { +// fmt.format(w, n); +// } +// +// @Override +// public void formatURI(AWriter w, Node n) { +// fmt.formatURI(w, n); +// } +// +// @Override +// public void formatURI(AWriter w, String uriStr) { +// fmt.formatURI(w, uriStr); +// } +// +// @Override +// public void formatVar(AWriter w, Node n) { +// fmt.formatVar(w, n); +// } +// +// @Override +// public void formatVar(AWriter w, String name) { +// fmt.formatVar(w, name); +// } +// +// @Override +// public void formatBNode(AWriter w, Node n) { +// fmt.formatBNode(w, n); +// } +// +// @Override +// public void formatBNode(AWriter w, String label) { +// fmt.formatBNode(w, label); +// } +// +// @Override +// public void formatLiteral(AWriter w, Node n) { +// fmt.formatLiteral(w, n); +// } +// +// @Override +// public void formatLitString(AWriter w, String lex) { +// fmt.formatLitString(w, lex); +// } +// +// @Override +// public void formatLitLang(AWriter w, String lex, String langTag) { +// fmt.formatLitLang(w, lex, langTag); +// } +// +// @Override +// public void formatLitDT(AWriter w, String lex, String datatypeURI) { +// fmt.formatLitDT(w, lex, datatypeURI); +// } +// } + + @Override + public void sendToken(Token token) { + String string = tokenToString(token); + write(string); + gap(true); + } + + @Override + public void sendNode(Node node) { + fmt.format(out, node); + gap(false); + } + + @Override + public void sendString(String string) { + fmt.formatLitString(out, string); + gap(false); + } + + @Override + public void sendWord(String string) { + write(string) ; // no escapes, no quotes + gap(true); + } + +// @Override +// public void sendControl(char controlChar) { +// String x = cntrlAsString(controlChar); +// write(x); +// gap(false); +// } + + @Override + public void sendNumber(long number) { + write(Long.toString(number)); + gap(true); + } + + @Override + public void startTuple() {} + + @Override + public void endTuple() { + if ( !inTuple ) + return; + out.write(Chars.CH_DOT); + out.write("\n"); + inTuple = false; + // If setup so that any added layers are not buffering, only the passed-in + // InputStream or Writer, then this is not necessary. + // flush(); + } + + @Override + public void close() { + if ( inTuple ) {} + IO.close(out); + } + + @Override + public void flush() { + out.flush(); + } + + // -------- + + private String tokenToString(Token token) { + switch (token.getType()) { + // superclass case NODE: + case IRI : + return "<" + token.getImage() + ">"; + case PREFIXED_NAME : + notImplemented(token); + return null; + case BNODE : + return "_:" + token.getImage(); + //case BOOLEAN: + case STRING : + return "\"" + FmtUtils.stringEsc(token.getImage()) + "\""; + case LITERAL_LANG : + return "\"" + FmtUtils.stringEsc(token.getImage()) + "\"@" + token.getImage2(); + case LITERAL_DT : + return "\"" + FmtUtils.stringEsc(token.getImage()) + "\"^^" + tokenToString(token.getSubToken2()); + case INTEGER : + case DECIMAL : + case DOUBLE : + return token.getImage(); + + // Not RDF + case KEYWORD : + return token.getImage(); + case DOT : + return Chars.S_DOT; + case VAR : + return "?" + token.getImage(); + case COMMA : + return Chars.S_COMMA; + case SEMICOLON : + return Chars.S_SEMICOLON; + case COLON : + return Chars.S_COLON; + case LT : + return Chars.S_LT; + case GT : + return Chars.S_GT; + case LE : + return Chars.S_LE; + case GE : + return Chars.S_GE; + case UNDERSCORE : + return Chars.S_UNDERSCORE; + case LBRACE : + return Chars.S_LBRACE; + case RBRACE : + return Chars.S_RBRACE; + case LPAREN : + return Chars.S_LPAREN; + case RPAREN : + return Chars.S_RPAREN; + case LBRACKET : + return Chars.S_LBRACKET; + case RBRACKET : + return Chars.S_RBRACKET; + case PLUS : + return Chars.S_PLUS; + case MINUS : + return Chars.S_MINUS; + case STAR : + return Chars.S_STAR; + case SLASH : + return Chars.S_SLASH; + case RSLASH : + return Chars.S_RSLASH; + case HEX : + return "0x" + token.getImage(); + // Syntax + // COLON is only visible if prefix names are not being processed. + case DIRECTIVE : + return "@" + token.getImage(); + case VBAR : + return Chars.S_VBAR; + case AMPERSAND : + return Chars.S_AMPHERSAND; + case EQUALS : + return Chars.S_EQUALS; + default : + notImplemented(token); + return null; + + // case EOF: + +// case EQUIVALENT : +// return "=="; +// case LOGICAL_AND : +// return "&&"; +// case LOGICAL_OR : +// return "||"; +// case NL : +// break; +// case NODE : +// break; +// case WS : +// break; + } + } + + // A gap is always necessary for items that are not endLog-limited. + // For example, numbers adjacent to numbers must have a gap but + // quoted string then quoted string does not require a gap. + private void gap(boolean required) { + if ( required || GAPS ) + write(" "); + } + + // Beware of multiple stringing. + private void write(String string) { + inTuple = true; + out.write(string); + } + + private static void exception(IOException ex) { + throw new RiotException(ex); + } + + private void notImplemented(Token token) { + throw new RiotException("Unencodable token: " + token); + } +} diff --git a/jena-rdfpatch/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle b/jena-rdfpatch/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle new file mode 100644 index 00000000000..efa13879605 --- /dev/null +++ b/jena-rdfpatch/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle @@ -0,0 +1 @@ +org.apache.jena.rdfpatch.system.InitPatch diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/AbstractTestPatchIO.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/AbstractTestPatchIO.java new file mode 100644 index 00000000000..b62fbb9be4f --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/AbstractTestPatchIO.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.function.Consumer; + +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.rdfpatch.changes.RDFChangesCollector; +import org.apache.jena.sparql.sse.SSE; +import org.junit.Test; + +public abstract class AbstractTestPatchIO { + // Write-read. + + private static Node g1 = SSE.parseNode(":g1"); + private static Node g2 = SSE.parseNode("_:g2"); + private static Node s1 = SSE.parseNode(":s1"); + private static Node s2 = SSE.parseNode("_:s2"); + private static Node s3 = SSE.parseNode("<<_:b :y 123>>"); + private static Node p1 = SSE.parseNode(""); + private static Node p2 = SSE.parseNode(":p2"); + private static Node o1 = SSE.parseNode(""); + private static Node o2 = SSE.parseNode("123"); + private static Node o3 = SSE.parseNode("<< <<_:b :q _:b>> :prop _:b >>"); + + // Dubious + private static Node zo1 = SSE.parseNode("'abc\uFFFDdef'"); + // Dubious + private static Node zs1 = NodeFactory.createURI("http://example/space uri"); + + + protected abstract void write(OutputStream out, RDFPatch patch); + protected abstract RDFPatch read(InputStream in); + + private byte[] write(RDFPatch patch) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(out, patch); + return out.toByteArray(); + } + + protected RDFPatch read(byte[] bytes) { + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + return read(in); + } + + private static RDFPatch makePatch(Consumer action) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + RDFChangesCollector changes = new RDFChangesCollector(); + changes.start(); + action.accept(changes); + changes.finish(); + RDFPatch patch = changes.getRDFPatch(); + return patch; + } + + private byte[] write(Consumer action) { + RDFPatch patch = makePatch(changes->action.accept(changes)); + byte[] output = write(patch); + return output; + } + + private void write_read(Consumer action) { + RDFPatch patch1 = makePatch(changes->action.accept(changes)); + byte[] bytes = write(patch1); + RDFPatch patch2 = read(bytes); + + if ( ! patch1.equals(patch2) ) { + System.out.println("<<<<"); + RDFPatchOps.write(System.out, patch1); + System.out.println("----"); + RDFPatchOps.write(System.out, patch2); + System.out.println(">>>>"); + } + + // Stored patches have .equals by value. + // Need recursion on <<>> + assertEquals(patch1, patch2); + } + + @Test public void write_read_01() { + write_read(changes->{ + changes.txnBegin(); + changes.add(g1, s1, p1, o1); + changes.delete(g1, s1, p1, o1); + changes.txnCommit(); + }); + } + + @Test public void write_read_02() { + write_read(changes->{ + changes.txnBegin(); + changes.add(g2, s2, p2, o2); + changes.txnCommit(); + }); + } + + @Test public void write_read_03() { + write_read(changes->{ + changes.txnBegin(); + changes.add(g2, s3, p2, o1); + //changes.add(g2, s3, p2, o3); + changes.txnCommit(); + }); + } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/ErrorHandlerTestLib.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/ErrorHandlerTestLib.java new file mode 100644 index 00000000000..10d2761d347 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/ErrorHandlerTestLib.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import java.util.ArrayList ; +import java.util.List ; + +import org.apache.jena.riot.system.ErrorHandler ; + +/** Error handler to convert anything to an exception */ +public class ErrorHandlerTestLib +{ + /** Specific exceptions for each error handler call */ + public static ErrorHandler asExceptions() { return new ErrorHandlerEx(); } + + public static class ExFatal extends RuntimeException { ExFatal(String msg) { super(msg) ; } } + + public static class ExError extends RuntimeException { ExError(String msg) { super(msg) ; } } + + public static class ExWarning extends RuntimeException { ExWarning(String msg) { super(msg) ; } } + + public static class ErrorHandlerEx implements ErrorHandler + { + public ErrorHandlerEx() {} + + @Override + public void warning(String message, long line, long col) + { throw new ExWarning(message) ; } + + @Override + public void error(String message, long line, long col) + { throw new ExError(message) ; } + + @Override + public void fatal(String message, long line, long col) + { throw new ExFatal(message) ; } + } + + // Error handler that records messages + public static class ErrorHandlerMsg implements ErrorHandler + { + public List msgs = new ArrayList<>() ; + + @Override + public void warning(String message, long line, long col) + { msgs.add(message) ; } + + @Override + public void error(String message, long line, long col) + { msgs.add(message) ; } + + @Override + public void fatal(String message, long line, long col) + { msgs.add(message) ; throw new ExFatal(message) ; } + } + +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java new file mode 100644 index 00000000000..625adab7207 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import org.apache.jena.atlas.logging.LogCtl; +import org.apache.jena.rdfpatch.filelog.TestAssemblerFileLog; +import org.apache.jena.rdfpatch.filelog.TestRotate; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses( { + TestPatchIO_Text.class + , TestPatchIO_Binary.class + , TestRDFChanges.class + , TestRDFChangesDataset.class + , TestRDFChangesGraph.class + , TestRDFChangesCancel.class + , TestRotate.class + , TestAssemblerFileLog.class +}) + +public class TS_RDFPatch { + static { LogCtl.setJavaLogging(); } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchIO_Binary.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchIO_Binary.java new file mode 100644 index 00000000000..78dde0bd35a --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchIO_Binary.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import java.io.InputStream; +import java.io.OutputStream; + +public class TestPatchIO_Binary extends AbstractTestPatchIO { + + @Override + protected void write(OutputStream out, RDFPatch path) { + RDFPatchOps.writeBinary(out, path); + } + + @Override + protected RDFPatch read(InputStream in) { + return RDFPatchOps.readBinary(in); + } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchIO_Text.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchIO_Text.java new file mode 100644 index 00000000000..8724b97a1c8 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchIO_Text.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.jena.atlas.lib.Bytes; +import org.apache.jena.riot.system.ErrorHandler; +import org.apache.jena.riot.system.ErrorHandlerFactory; +import org.junit.Test; + +public class TestPatchIO_Text extends AbstractTestPatchIO { + + private static void noWarning(Runnable action) { + } + + @Override + protected void write(OutputStream out, RDFPatch path) { + RDFPatchOps.write(out, path); + } + + @Override + protected RDFPatch read(InputStream in) { + return RDFPatchOps.read(in, ErrorHandlerFactory.errorHandlerExceptionOnError()); + } + + private static String DIR = "testing/files/"; + + @Test + public void readPatch_1() { + RDFPatchOps.read(DIR+"syntax-1.rdfp"); + } + + private static ErrorHandler ehExceptions = ErrorHandlerTestLib.asExceptions(); + + private static RDFPatch parseSyntax(String string) { + byte[] bytes = Bytes.string2bytes(string); + InputStream input = new ByteArrayInputStream(bytes); + RDFPatch patch = RDFPatchOps.read(input, ehExceptions); + return patch; + } + + @Test(expected=ErrorHandlerTestLib.ExWarning.class) + public void read_warning_01() { + String str = "A 'abc\uFFFDdef' ."; + parseSyntax(str); + } + + @Test public void read_no_warning_01() { + // Explicit escape - accepted. + String str = "A 'abc\\uFFFDdef' ."; + parseSyntax(str); + } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChanges.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChanges.java new file mode 100644 index 00000000000..603885b2b7a --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChanges.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.io.ByteArrayOutputStream; +import java.util.function.Consumer; + +import org.apache.jena.atlas.lib.StrUtils; +import org.apache.jena.graph.Node; +import org.apache.jena.rdfpatch.changes.PatchSummary; +import org.apache.jena.rdfpatch.changes.RDFChangesCollector; +import org.apache.jena.rdfpatch.changes.RDFChangesCounter; +import org.apache.jena.rdfpatch.changes.RDFChangesN; +import org.apache.jena.sparql.sse.SSE; +import org.junit.Test; + +public class TestRDFChanges { + private static Node g1 = SSE.parseNode(":g1"); + private static Node g2 = SSE.parseNode("_:g2"); + private static Node s1 = SSE.parseNode(":s1"); + private static Node s2 = SSE.parseNode("_:s2"); + private static Node p1 = SSE.parseNode(""); + private static Node p2 = SSE.parseNode(":p2"); + private static Node o1 = SSE.parseNode(""); + private static Node o2 = SSE.parseNode("123"); + + private static RDFPatch makePatch(Consumer action) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + RDFChangesCollector changes = new RDFChangesCollector(); + changes.start(); + action.accept(changes); + changes.finish(); + RDFPatch patch = changes.getRDFPatch(); + return patch; + } + + private static byte[] write(Consumer action) { + RDFPatch patch = makePatch(changes->action.accept(changes)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + RDFPatchOps.write(out, patch); + byte[] output = out.toByteArray(); + return output; + } + + // test basic mechanism + @Test public void changes_01() { + RDFChangesCollector changes = new RDFChangesCollector(); + changes.start(); + changes.txnBegin(); + changes.add(g1, s1, p1, o1); + changes.txnCommit(); + changes.finish(); + } + + @Test public void changes_02() { + RDFPatch patch = makePatch(changes->{ + changes.add(g1, s1, p1, o1); + changes.add(g1, s1, p1, o1); + }); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + RDFPatchOps.write(out, patch); + byte[] output = out.toByteArray(); + } + + @Test public void changes_03() { + RDFPatch patch = makePatch(changes->{ + changes.add(g1, s1, p1, o1); + changes.add(g1, s1, p1, o1); + }); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + RDFPatchOps.write(out, patch); + byte[] output = out.toByteArray(); + byte[] output2 = write(changes->{ + changes.add(g1, s1, p1, o1); + changes.add(g1, s1, p1, o1); + }); + assertArrayEquals(output, output2); + } + + @Test public void changes_04() { + byte[] output = write(changes->{}); + assertEquals(0, output.length); + String x = StrUtils.fromUTF8bytes(output); + } + + @Test public void changes_05() { + byte[] output = write(changes->{ + changes.txnBegin(); + changes.txnCommit(); + }); + assertNotEquals(0, output.length); + String x = StrUtils.fromUTF8bytes(output); + assertEquals("TX .\nTC .\n", x); + } + + @Test public void changes_prefix_01() { + RDFPatch patch = makePatch((x)->{ + x.txnBegin(); + x.deletePrefix(g2, "ex"); + x.addPrefix(g1, "ex", "http://example/"); + x.addPrefix(g2, "ex", "http://example/"); + x.txnCommit(); + }); + RDFChangesCounter changes = new RDFChangesCounter(); + patch.apply(changes); + PatchSummary ps = changes.summary(); + assertEquals(1, ps.getCountTxnBegin()); + assertEquals(1, ps.getCountTxnCommit()); + assertEquals(0, ps.getCountTxnAbort()); + assertEquals(2, ps.getCountAddPrefix()); + assertEquals(1, ps.getCountDeletePrefix()); + } + + // Specific implementations. + + @Test public void changesN_01() { + RDFPatch patch = makePatch((x)->{ + x.txnBegin(); + x.add(g1, s1, p1, o1); + x.add(g2, s1, p1, o1); + x.txnCommit(); + }); + + RDFChangesCounter c1 = new RDFChangesCounter(); + RDFChangesCounter c2 = new RDFChangesCounter(); + RDFChanges changes = new RDFChangesN(c1, c2); + patch.apply(changes); + assertEquals(1, c1.summary().getCountTxnBegin()); + assertEquals(1, c1.summary().getCountTxnCommit()); + assertEquals(0, c1.summary().getCountTxnAbort()); + assertEquals(2, c1.summary().getCountAddData()); + + assertEquals(1, c2.summary().getCountTxnBegin()); + assertEquals(1, c2.summary().getCountTxnCommit()); + assertEquals(0, c2.summary().getCountTxnAbort()); + assertEquals(2, c2.summary().getCountAddData()); + } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesCancel.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesCancel.java new file mode 100644 index 00000000000..177cae09e18 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesCancel.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import static org.junit.Assert.assertEquals; + +import org.apache.jena.graph.Triple; +import org.apache.jena.rdfpatch.changes.PatchSummary; +import org.apache.jena.rdfpatch.changes.RDFChangesCounter; +import org.apache.jena.rdfpatch.system.DatasetGraphChanges; +import org.apache.jena.rdfpatch.system.RDFChangesSuppressEmpty; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.system.Txn; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class TestRDFChangesCancel { + + private final RDFChangesCounter counter; + private final DatasetGraph dsg; + { + counter = new RDFChangesCounter(); + RDFChanges c = new RDFChangesSuppressEmpty(counter); + DatasetGraph dsg0 = DatasetGraphFactory.createTxnMem(); + dsg = new DatasetGraphChanges(dsg0, c); + } + + @Before public void beforeTest() { } + + @After public void afterTest() { } + + private static Quad quad1 = SSE.parseQuad("(:g _:s

1)"); + private static Quad quad2 = SSE.parseQuad("(:g _:s

2)"); + + private static Triple triple1 = SSE.parseTriple("(_:sx 11)"); + private static Triple triple2 = SSE.parseTriple("(_:sx 22)"); + + @Test public void changeSuppressEmptyCommit_1() { + Txn.executeRead(dsg, ()->{}); + + PatchSummary s1 = counter.summary(); + assertEquals(0, s1.getCountTxnBegin()); + assertEquals(0, s1.getCountTxnCommit()); + assertEquals(0, s1.getCountTxnAbort()); + } + + @Test public void changeSuppressEmptyCommit_2() { + Txn.executeWrite(dsg, ()->{}); + + PatchSummary s1 = counter.summary(); + assertEquals(1, s1.getCountTxnBegin()); + assertEquals(0, s1.getCountTxnCommit()); + assertEquals(1, s1.getCountTxnAbort()); + } + + @Test public void changeSuppressEmptyCommit_3() { + Txn.executeWrite(dsg, ()->dsg.add(quad1)); + + PatchSummary s1 = counter.summary(); + assertEquals(1, s1.getCountTxnBegin()); + assertEquals(1, s1.getCountTxnCommit()); + assertEquals(0, s1.getCountTxnAbort()); + } + + + @Test public void changeSuppressEmptyCommit_4() { + Quad q = SSE.parseQuad("(_ :s :p 'object')"); + Triple t = SSE.parseTriple("(:t :p 'object')"); + + Txn.executeRead(dsg, ()->{}); + testCounters(counter.summary(), 0, 0); + + Txn.executeWrite(dsg, ()->{dsg.add(q);}); + testCounters(counter.summary(), 1, 0); + + Txn.executeWrite(dsg, ()->{dsg.getDefaultGraph().add(t);}); + testCounters(counter.summary(), 2, 0); + + Txn.executeWrite(dsg, ()->{dsg.getDefaultGraph().getPrefixMapping().setNsPrefix("", "http://example/");}); + testCounters(counter.summary(), 2, 1); + + Txn.executeWrite(dsg, ()->{}); + testCounters(counter.summary(), 2, 1); + } + + private static void testCounters(PatchSummary s, long dataAddCount, long prefixAddCount) { + assertEquals("dataAddCount", dataAddCount, s.getCountAddData()); + assertEquals("prefixAddCount", prefixAddCount, s.getCountAddPrefix()); + } + +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesDataset.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesDataset.java new file mode 100644 index 00000000000..d2a21b6a733 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesDataset.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.iterator.Iter; +import org.apache.jena.atlas.lib.ListUtils; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.graph.GraphFactory; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.system.Txn; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Check one dataset tracks another. */ +public class TestRDFChangesDataset { + DatasetGraph dsgBase = DatasetGraphFactory.createTxnMem(); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DatasetGraph dsg = RDFPatchOps.textWriter(dsgBase, bout); + + @Before public void beforeTest() {} + @After public void afterTest() {} + + private static Quad quad1 = SSE.parseQuad("(:g _:s

1)"); + private static Quad quad2 = SSE.parseQuad("(:g _:s

2)"); + + private static Triple triple1 = SSE.parseTriple("(_:sx 11)"); + private static Triple triple2 = SSE.parseTriple("(_:sx 22)"); + + private DatasetGraph replay() { + IO.close(bout); + try(ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray())) { + DatasetGraph dsg2 = DatasetGraphFactory.createTxnMem(); + RDFPatchOps.applyChange(dsg2, bin); + return dsg2; + } catch (IOException ex) { IO.exception(ex); return null; } + } + + private static void check(DatasetGraph dsg, Quad...quads) { + if ( quads.length == 0 ) { + assertTrue(dsg.isEmpty()); + return; + } + + List listExpected = Arrays.asList(quads); + List listActual = Iter.toList(dsg.find()); + assertEquals(listActual.size(), listExpected.size()); + assertTrue(ListUtils.equalsUnordered(listExpected, listActual)); + } + + @Test public void record_00() { + DatasetGraph dsg2 = replay(); + check(dsg2); + } + + @Test public void record_add() { + Txn.executeWrite(dsg, ()->dsg.add(quad1)); + DatasetGraph dsg2 = replay(); + check(dsg2, quad1); + } + + @Test public void record_add_add_1() { + Txn.executeWrite(dsg, ()-> { + dsg.add(quad1); + dsg.add(quad2); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, quad1, quad2); + } + + @Test public void record_add_add_2() { + Txn.executeWrite(dsg, ()-> { + dsg.add(quad1); + dsg.add(quad1); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, quad1); + } + + @Test public void record_add_delete_1() { + Txn.executeWrite(dsg, ()-> { + dsg.add(quad1); + dsg.delete(quad1); + }); + DatasetGraph dsg2 = replay(); + check(dsg2); + } + + @Test public void record_add_delete_2() { + Txn.executeWrite(dsg, ()-> { + dsg.add(quad1); + dsg.delete(quad2); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, quad1); + } + + @Test public void record_add_delete_3() { + Txn.executeWrite(dsg, ()-> { + dsg.delete(quad2); + dsg.add(quad1); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, quad1); + } + + @Test public void record_add_delete_4() { + Txn.executeWrite(dsg, ()-> { + dsg.delete(quad1); + dsg.add(quad1); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, quad1); + } + + @Test public void record_add_abort_1() { + Txn.executeWrite(dsg, ()-> { + dsg.delete(quad1); + dsg.abort(); + }); + DatasetGraph dsg2 = replay(); + check(dsg2); + } + + @Test public void record_graph_1() { + Txn.executeWrite(dsg, ()-> { + Graph g = dsg.getDefaultGraph(); + g.add(triple1); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, Quad.create(Quad.defaultGraphIRI, triple1)); + } + + @Test public void record_graph_2() { + Txn.executeWrite(dsg, ()-> { + Graph g = dsg.getDefaultGraph(); + g.add(triple1); + g.delete(triple1); + g.add(triple2); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, Quad.create(Quad.defaultGraphIRI, triple2)); + } + + @Test public void record_graph_3() { + Txn.executeWrite(dsg, ()-> { + Graph g = dsg.getDefaultGraph(); + g.add(triple1); + dsg.delete(Quad.create(Quad.defaultGraphIRI, triple1)); + }); + DatasetGraph dsg2 = replay(); + check(dsg2); + } + + @Test public void record_remove_graph_1() { + Txn.executeWrite(dsg, ()->dsg.add(quad1)); + Node gn = quad1.getGraph(); + + Txn.executeWrite(dsg, ()-> { + dsg.removeGraph(gn); + }); + + DatasetGraph dsg2 = replay(); + check(dsg2); + } + + @Test public void record_add_graph_1() { + Txn.executeWrite(dsg, ()->dsg.add(quad1)); + + Graph data = GraphFactory.createDefaultGraph(); + Node gn = quad1.getGraph(); + data.add(triple1); + Txn.executeWrite(dsg, ()-> { + dsg.addGraph(gn, data); + }); + DatasetGraph dsg2 = replay(); + check(dsg2, Quad.create(gn, triple1)); + } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesGraph.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesGraph.java new file mode 100644 index 00000000000..f59843cb109 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFChangesGraph.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.iterator.Iter; +import org.apache.jena.atlas.lib.ListUtils; +import org.apache.jena.atlas.lib.StrUtils; +import org.apache.jena.graph.*; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.shared.JenaException; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.sse.SSE; +import org.junit.Test; + +public class TestRDFChangesGraph { + + private static Graph txnGraph() { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + Graph g = dsg.getDefaultGraph(); + // Jena 3.5.0 and earlier. +// g = new GraphWrapper(g) { +// @Override public TransactionHandler getTransactionHandler() { return new TransactionHandlerViewX(dsg); } +// }; + return g; + } + private static Graph txnGraph(String graphName) { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + Node gn = NodeFactory.createURI(graphName); + Graph g = dsg.getGraph(gn); + // Jena 3.5.0 and earlier. +// g = new GraphWrapper(g) { +// @Override public TransactionHandler getTransactionHandler() { return new TransactionHandlerView(dsg); } +// }; + return g; + } + + private static Triple triple1 = SSE.parseTriple("(_:sx 11)"); + private static Triple triple2 = SSE.parseTriple("(_:sx 22)"); + + // Bytes for changes + private ByteArrayOutputStream bout; + // The underlying graph + private Graph baseGraph; + // The graph with changes wrapper. + private Graph graph; + + // ---- + // Replay a changes byte stream into a completely fresh graph + private Graph replay() { + IO.close(bout); + final boolean DEBUG = false; + + if ( DEBUG ) { + System.out.println("== Graph =="); + RDFDataMgr.write(System.out, baseGraph, Lang.NQ); + System.out.println("== Replay =="); + String x = StrUtils.fromUTF8bytes(bout.toByteArray()); + System.out.print(x); + System.out.println("== =="); + } + + // A completely separate graph (different dataset) + Graph graph2 = txnGraph(); + + try(ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray())) { + RDFPatchOps.applyChange(graph2, bin); + if ( DEBUG ) { + System.out.println("== Graph outcome =="); + RDFDataMgr.write(System.out, graph2, Lang.NT); + System.out.println("== =="); + } + return graph2; + } catch (IOException ex) { IO.exception(ex); return null; } + } + + private static void check(Graph graph, Triple...quads) { + if ( quads.length == 0 ) { + assertTrue(graph.isEmpty()); + return; + } + List listExpected = Arrays.asList(quads); + List listActual = Iter.toList(graph.find()); + assertEquals(listActual.size(), listExpected.size()); + assertTrue(ListUtils.equalsUnordered(listExpected, listActual)); + } + + private static void txn(Graph graph, Runnable action) { + graph.getTransactionHandler().execute(action); + } + + // ---- + + private void setup() { + this.bout = new ByteArrayOutputStream(); + this.baseGraph = txnGraph(); + this.graph = RDFPatchOps.textWriter(baseGraph, bout); + } + + private void setup(String graphName) { + this.bout = new ByteArrayOutputStream(); + this.baseGraph = txnGraph(graphName); + this.graph = RDFPatchOps.textWriter(baseGraph, bout); + } + + @Test public void record_00() { + setup(); + + Graph graph2 = replay(); + check(graph2); + } + + @Test public void record_add() { + setup(); + + txn(graph, ()->graph.add(triple1)); + Graph g2 = replay(); + check(g2, triple1); + } + + @Test public void record_add_add_1() { + setup(); + + txn(graph, ()-> { + graph.add(triple1); + graph.add(triple2); + }); + Graph g2 = replay(); + check(g2, triple1, triple2); + } + + + @Test public void record_add_delete_1() { + setup(); + + txn(graph, ()-> { + graph.add(triple1); + graph.delete(triple1); + }); + Graph g2 = replay(); + check(g2); + } + + + @Test public void record_add_abort_2() { + setup(); + TransactionHandler h = graph.getTransactionHandler(); + h.begin(); + graph.add(triple1); + h.abort(); + Graph g2 = replay(); + check(g2); + } + + @Test public void record_add_abort_1() { + setup(); + try { + txn(graph, ()->{ + graph.add(triple1); + throw new JenaException("Abort!"); + }); + } catch (JenaException ex) {} + Graph g2 = replay(); + check(g2); + } + + @Test public void record_named_graph_1() { + setup("http://example/graph"); + txn(graph, ()->graph.add(triple1)); + Graph g2 = replay(); + check(g2, triple1); + } +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/filelog/TestAssemblerFileLog.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/filelog/TestAssemblerFileLog.java new file mode 100644 index 00000000000..19904e6c3b9 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/filelog/TestAssemblerFileLog.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import static org.junit.Assert.assertTrue; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.logging.LogCtl; +import org.apache.jena.query.Dataset; +import org.apache.jena.rdfpatch.RDFPatch; +import org.apache.jena.rdfpatch.RDFPatchOps; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.core.assembler.AssemblerUtils; +import org.apache.jena.sparql.util.IsoMatcher; +import org.apache.jena.sys.JenaSystem; +import org.apache.jena.system.Txn; +import org.junit.BeforeClass; +import org.junit.Test; + +public class TestAssemblerFileLog { + static { + LogCtl.setJavaLogging(); + JenaSystem.init(); + } + + private static String ADIR = "testing/filelog"; + +// @Rule +// public TemporaryFolder tempFolder = new TemporaryFolder(); + + // We want to leave the evidence around on test failures. + private static Path DIR = Paths.get("target/filelog"); + + @BeforeClass + public static void beforeClass() { + FileOps.ensureDir(DIR.toString()); + FileOps.clearAll(DIR.toString()); + } + + @Test public void assem() { + Dataset ds = (Dataset)AssemblerUtils.build(ADIR+"/assem-logged.ttl", VocabPatch.tLoggedDataset); + } + + @Test public void assemData() { + Dataset ds = (Dataset)AssemblerUtils.build(ADIR+"/assem-logged.ttl", VocabPatch.tLoggedDataset); + Txn.executeWrite(ds, ()->RDFDataMgr.read(ds, ADIR+"/data.ttl")); + + String patchfile = "target/filelog/log.rdfp.0001"; + assertTrue("Patch file does not exist: "+patchfile, FileOps.exists(patchfile)); + RDFPatch patch = RDFPatchOps.read(patchfile); + DatasetGraph dsg1 = DatasetGraphFactory.createTxnMem(); + RDFPatchOps.applyChange(dsg1, patch); + // Same term, no bnode isomorphism. + boolean b = IsoMatcher.isomorphic(ds.asDatasetGraph(), dsg1); + assertTrue(b); + } + +} diff --git a/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/filelog/TestRotate.java b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/filelog/TestRotate.java new file mode 100644 index 00000000000..7f6f4f69715 --- /dev/null +++ b/jena-rdfpatch/src/test/java/org/apache/jena/rdfpatch/filelog/TestRotate.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.rdfpatch.filelog; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.logging.LogCtl; +import org.apache.jena.rdfpatch.filelog.rotate.ManagedOutput; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TestRotate +{ + static { LogCtl.setJavaLogging(); } + + @Parameters(name="Policy={0}") + public static Collection data() + { + List params = new ArrayList<>(); + for ( FilePolicy p : FilePolicy.values() ) { + Object[] x = {p, false}; + params.add(x); + } + return params; + } + + private final FilePolicy policy; + private final boolean includesBaseName; + + private static Path DIR = Paths.get("target/filelog"); + + @BeforeClass + public static void beforeClass() { + FileOps.ensureDir(DIR.toString()); + FileOps.clearAll(DIR.toString()); + } + + /** "touch" - without metadata */ + private static void touch(String filename) { + Path p = DIR.resolve(filename); + if ( ! Files.exists(p) ) { + try ( OutputStream out = new FileOutputStream(p.toString()) ) {} + catch (IOException ex) { + IO.exception(ex); + } + } + } + + // Test for file existance : pass the full filename. + private static void assertExists(String filename) { + Path p = Paths.get(filename); + boolean b = Files.exists(p); + assertTrue("File does not exist: "+p, b); + } + + private static void assertNotExists(String filename) { + Path p = Paths.get(filename); + boolean b = Files.exists(p); + assertFalse("File exists: "+p, b); + } + + private static void assertExists(Path dir, String filename) { + Path p = dir.resolve(filename); + boolean b = Files.exists(p); + assertTrue("File does not exist: "+p, b); + } + + public TestRotate(FilePolicy policy, Boolean includesBaseName) { + this.policy = policy; + this.includesBaseName = includesBaseName; + } + + @Test + public void t1_firstFile() throws IOException { + String FN = "fileA-"+policy.name(); + ManagedOutput mout = OutputMgr.create(DIR, FN, policy); + try ( OutputStream out = mout.output() ) { + PrintStream ps = new PrintStream(out, true, StandardCharsets.UTF_8.name()); + ps.print("abcdef"); + } + assertNotNull(mout.latestFilename()); + assertExists(mout.latestFilename().toString()); + } + + @Test + public void t2_rotateFile() throws IOException { + String FN = "fileB-"+policy.name(); + ManagedOutput mout = OutputMgr.create(DIR, FN, policy); + try ( OutputStream out = mout.output() ) { } + assertNotNull(mout.latestFilename()); + mout.rotate(); + try ( OutputStream out = mout.output() ) { } + assertNotNull(mout.latestFilename()); + String x2 = mout.latestFilename().toString(); + assertExists(x2); + } +} diff --git a/jena-rdfpatch/src/test/resources/logging.properties b/jena-rdfpatch/src/test/resources/logging.properties new file mode 100644 index 00000000000..165b30c8802 --- /dev/null +++ b/jena-rdfpatch/src/test/resources/logging.properties @@ -0,0 +1,17 @@ +handlers=org.apache.jena.atlas.logging.java.ConsoleHandlerStream +org.apache.jena.atlas.logging.java.ConsoleHandlerStream.level = ALL +.level=INFO + +## org.apache.jena.atlas.logging.java.ConsoleHandlerStream.level=INFO +## org.apache.jena.atlas.logging.java.ConsoleHandlerStdout.formatter = \ +## org.apache.jena.atlas.logging.java.TextFormatter +## org.apache.jena.atlas.logging.java.TextFormatter.format = \ +## default: %5$tT %3$-5s %2$-20s :: %6$s +## Full name %1 and milliseconds %5$tL %$ +## date/time : [%5$tF %5$tT] +## %5$tT.%5$tL %3$-5s %1$-20s :: %6$s + +org.apache.jena.atlas.logging.java.TextFormatter.format = %5$tT %3$-5s %2$-20s : %6$s + +org.seaborne.patch.level = WARNING +org.apache.jena.level = INFO diff --git a/jena-rdfpatch/testing/filelog/assem-logged.ttl b/jena-rdfpatch/testing/filelog/assem-logged.ttl new file mode 100644 index 00000000000..3b0e880ff00 --- /dev/null +++ b/jena-rdfpatch/testing/filelog/assem-logged.ttl @@ -0,0 +1,18 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX patch: +PREFIX rdf: +PREFIX rdfs: +PREFIX tdb2: +PREFIX ja: + +<#dataset> rdf:type patch:LoggedDataset ; + patch:log "target/filelog/log.rdfp" ; + patch:logPolicy "INDEX"; + ja:dataset <#dataset2> ; + . + +<#dataset2> rdf:type ja:MemoryDataset . + + \ No newline at end of file diff --git a/jena-rdfpatch/testing/filelog/data.ttl b/jena-rdfpatch/testing/filelog/data.ttl new file mode 100644 index 00000000000..0356a51f6f5 --- /dev/null +++ b/jena-rdfpatch/testing/filelog/data.ttl @@ -0,0 +1,4 @@ +PREFIX : + +:s :p :o . +:s :p [] . diff --git a/jena-rdfpatch/testing/files/syntax-1.rdfp b/jena-rdfpatch/testing/files/syntax-1.rdfp new file mode 100644 index 00000000000..8c53b66ecdb --- /dev/null +++ b/jena-rdfpatch/testing/files/syntax-1.rdfp @@ -0,0 +1,10 @@ +# Text patch. +H id . +TX . +PA "ex" . +PD "ex" . +PA "" . +PD "" . +A . +D . +TC . diff --git a/jena-rdfpatch/testing/files/syntax-bad-1.rdfp b/jena-rdfpatch/testing/files/syntax-bad-1.rdfp new file mode 100644 index 00000000000..727fa9566c8 --- /dev/null +++ b/jena-rdfpatch/testing/files/syntax-bad-1.rdfp @@ -0,0 +1,5 @@ +# Text patch. +H id . +TX . +A "\uFFFD" . +TC . diff --git a/pom.xml b/pom.xml index b8f1f9f02e4..ed7d5a21fba 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,7 @@ jena-arq jena-shacl jena-shex + jena-rdfpatch jena-rdfconnection jena-db jena-tdb1 @@ -243,6 +244,7 @@ jena-arq jena-shacl jena-shex + jena-rdfpatch jena-rdfconnection jena-db jena-tdb1