001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.jena;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.IoUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.marshall.jena.Constants.*;
024
025import java.io.*;
026import java.util.*;
027import java.util.function.*;
028
029import org.apache.jena.rdf.model.*;
030import org.apache.jena.riot.*;
031import org.apache.juneau.commons.bean.*;
032import org.apache.juneau.commons.utils.*;
033import org.apache.juneau.marshall.*;
034import org.apache.juneau.marshall.serializer.*;
035import org.apache.juneau.marshall.xml.*;
036
037/**
038 * Session object that lives for the duration of a single use of {@link RdfSerializer}.
039 *
040 * <h5 class='section'>Notes:</h5><ul>
041 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
042 * </ul>
043 *
044 * <h5 class='section'>See Also:</h5><ul>
045 *    <li class='link'>{doc jmr.RdfDetails}
046
047 * </ul>
048 */
049@SuppressWarnings({
050   "java:S110", // Deep inheritance inherent to the RDF serializer session hierarchy.
051   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
052   "java:S2176", // Inheritance depth exceeds 5; necessary to participate in the serializer session hierarchy
053   "java:S3776", // Cognitive complexity acceptable for RDF serializer session methods
054   "java:S6541", // Brain Method complexity acceptable for core RDF serializer dispatch logic
055   "rawtypes", // Raw types necessary for generic type handling
056   "unchecked" // Type erasure requires unchecked casts
057})
058public class RdfSerializerSession extends WriterSerializerSession {
059
060   /**
061    * Builder class.
062    */
063   public static class Builder extends WriterSerializerSession.Builder<Builder> {
064
065      private final RdfSerializer ctx;
066
067      /**
068       * Constructor
069       *
070       * @param ctx The context creating this session.
071       *    <br>Cannot be <jk>null</jk>.
072       */
073      protected Builder(RdfSerializer ctx) {
074         super(reqnn("ctx", ctx));
075         this.ctx = ctx;
076      }
077
078      @Override
079      public RdfSerializerSession build() {
080         return new RdfSerializerSession(this);
081      }
082
083   }
084
085   /**
086    * Maps RDF writer names to property prefixes that apply to them.
087    */
088   // @formatter:off
089   static final Map<String,String> LANG_PROP_MAP = mb(String.class, String.class)
090      .unmodifiable()
091      .add("RDF/XML", "rdfXml.")
092      .add("RDF/XML-ABBREV", "rdfXml.")
093      .add("N3", "n3.")
094      .add("N3-PP", "n3.")
095      .add("N3-PLAIN", "n3.")
096      .add("N3-TRIPLES", "n3.")
097      .add("TURTLE", "n3.")
098      .add("N-TRIPLE", "ntriple.")
099      .add("JSON-LD", "jsonLd.")
100      .add("N-QUADS", "nquads.")
101      .add("TRIG", "trig.")
102      .add("TRIX", "trix.")
103      .add("RDF/JSON", "rdfJson.")
104      .build();
105   // @formatter:on
106
107   /**
108    * Creates a new builder for this object.
109    *
110    * @param ctx The context creating this session.
111    *    <br>Cannot be <jk>null</jk>.
112    * @return A new builder.
113    */
114   public static Builder create(RdfSerializer ctx) {
115      return new Builder(reqnn("ctx", ctx));
116   }
117
118   private final Model model;
119   private final Property pRoot;
120   private final Property pValue;
121   private final Lang lang;
122   private final RdfSerializer ctx;
123   private final Namespace[] namespaces;
124
125   /**
126    * Constructor.
127    *
128    * @param builder The builder for this object.
129    *    <br>Cannot be <jk>null</jk>.
130    */
131   protected RdfSerializerSession(Builder builder) {
132      super(builder);
133      ctx = builder.ctx;
134
135      model = ModelFactory.createDefaultModel();
136      namespaces = ctx.namespaces;
137      addModelPrefix(ctx.getJuneauNs());
138      addModelPrefix(ctx.getJuneauBpNs());
139      for (var ns : this.namespaces)
140         addModelPrefix(ns);
141      pRoot = model.createProperty(ctx.getJuneauNs().getUri(), "root");
142      pValue = model.createProperty(ctx.getJuneauNs().getUri(), "value");
143
144      // Map legacy language names to RIOT Lang (e.g. "N-TRIPLE" -> NTRIPLES)
145      var langName = ctx.getLanguage();
146      lang = toLang(langName);
147      if (lang == null)
148         throw rex("Unknown RDF language encountered: '%s'", langName);
149   }
150
151   // Package-private (not private) for white-box testing.
152   static Lang toLang(String langName) {
153      var lang = RDFLanguages.nameToLang(langName);
154      if (lang != null)
155         return lang;
156      if (eq(langName, "RDF/PROTO"))
157         return Lang.RDFPROTO;
158      return null;
159   }
160
161   /*
162    * Adds the specified namespace as a model prefix.
163    */
164   private void addModelPrefix(Namespace ns) {
165      model.setNsPrefix(ns.getName(), ns.getUri());
166   }
167
168   /**
169    * XML-encoded the specified element name using the {@link XmlUtils#encodeElementName(Object)} method.
170    */
171   private String encodeElementName(Object o) {
172      return XmlUtils.encodeElementName(toString(o));
173   }
174
175   /**
176    * XML-encodes the specified string using the {@link XmlUtils#escapeText(Object)} method.
177    */
178   // Package-private (not private) for white-box testing.
179   String encodeTextInvalidChars(Object o) {
180      if (o == null)
181         return null;
182      var s = toString(o);
183      return XmlUtils.escapeText(s);
184   }
185
186   private String getUri(Object uri) {
187      if (! nn(uri))
188         return null;
189      return getUriResolver().resolve(uri.toString());
190   }
191
192   @SuppressWarnings({
193      "null" // Null analysis not applicable to RDF serialization
194   })
195   private RDFNode writeAnything(Object o, boolean isURI, ClassMeta<?> eType, String attrName, BeanPropertyMeta bpm, Resource parentResource) throws SerializeException {
196      var m = model;
197
198      ClassMeta<?> wType = null;            // The wrapped type
199      ClassMeta<?> sType;                   // The serialized type
200
201      var aType = push2(attrName, o, eType);  // The actual type
202
203      if (eType == null)
204         eType = object();
205
206      // Handle recursion
207      if (aType == null) {
208         o = null;
209         aType = object();
210      }
211
212      // Handle Optional values
213      if (isOptional(aType)) {
214         o = getOptionalValue(o);
215         eType = getOptionalType(eType);
216         aType = getClassMetaForObject(o, object());
217      }
218
219      if (nn(o)) {
220
221         if (aType.isDelegate()) {
222            wType = aType;
223            aType = (ClassMeta)((Delegate)o).getBeanInfo();
224         }
225
226         sType = aType;
227
228         // Swap if necessary
229         var swap = aType.getSwap(this);
230         if (nn(swap)) {
231            o = swap(swap, o);
232            sType = swap.getSwapClassMeta(this);
233
234            // If the getSwapClass() method returns Object, we need to figure out
235            // the actual type now.
236            if (sType.isObject())
237               sType = getClassMetaForObject(o);
238         }
239      } else {
240         sType = eType.getSerializedClassMeta(this);
241      }
242
243      var typeName = getBeanTypeName(this, eType, aType, bpm);
244
245      RDFNode n = null;
246
247      if (o == null || sType.isChar() && ((Character)o).charValue() == 0) {
248         if (nn(bpm)) {
249            if (isKeepNullProperties()) // HTT: bpm!=null only reaches this branch via writeBeanMap's checkNull-filtered call site, which only lets a null/NUL value through when isKeepNullProperties() is already true. So whenever this line executes, isKeepNullProperties() is structurally guaranteed true; the false side (leaving n unset) is dead in this call path.
250               n = m.createResource(RDF_NIL);
251         } else {
252            n = m.createResource(RDF_NIL);
253         }
254
255      } else if (sType.isUri() || isURI) {
256         // RDF URI gate must come before isBean/isMap/isCharSequence: values annotated with Uri (where sType could be String or a bean) need to route through the Resource emission path.  RDF URIs must be absolute to be valid.
257         var uri = getUri(o);
258         if (isAbsoluteUri(uri))
259            n = m.createResource(uri);
260         else
261            n = m.createLiteral(encodeTextInvalidChars(uri));
262
263      } else if (sType.isBean()) {
264         var bm = toBeanMap(o);
265         Object uri = null;
266         RdfBeanMeta rbm = getRdfBeanMeta(bm.getMeta());
267         if (rbm.hasBeanUri())
268            uri = rbm.getBeanUriProperty().get(bm, null);
269         String uri2 = getUri(uri);
270         n = m.createResource(uri2);
271         writeBeanMap(bm, (Resource)n, typeName);
272
273      } else if (sType.isMap() || (nn(wType) && wType.isMap())) { // HTT: 1 residual branch -- the "wType.isMap()" right-hand sub-check is evaluated only when sType.isMap()==false AND nn(wType)==true, but the only concrete Delegate wrapper reaching this far, DelegateMap (internal.DelegateMap#getBeanInfo()), always returns a MAP-category ClassMeta, so sType.isMap() is already true before wType is even consulted; the sub-check's own "true" outcome is therefore never separately exercised (short-circuited away every time). Same root cause as the isCollectionOrArray() check below.
274         if (o instanceof BeanMap o2) { // HTT: suspected structurally dead. The only way to reach this else-if with wType!=null is via a Delegate: for BeanMap specifically (which IS a Delegate, and IS a java.util.Map), aType gets reassigned to the WRAPPED bean's own BEAN-category ClassMeta, so sType ends up isBean()==true and dispatches through the EARLIER sType.isBean() branch instead -- this else-if is never reached for a real BeanMap. For the OTHER disjunct (sType.isMap() directly true, e.g. an ordinary Map/JsonMap), `o` is never itself a BeanMap. Flagged, not deleted -- see also RdfStreamSerializerSession, which has the identical pattern (and an empirical confirmation via its o10_stream_serialize_rawBeanMap_direct test).
275            Object uri = null; // HTT: unreachable -- see guard above.
276            var rbm = getRdfBeanMeta(o2.getMeta()); // HTT: unreachable -- see guard above.
277            if (rbm.hasBeanUri()) // HTT: unreachable -- see guard above.
278               uri = rbm.getBeanUriProperty().get(o2, null); // HTT: unreachable -- see guard above.
279            var uri2 = getUri(uri); // HTT: unreachable -- see guard above.
280            n = m.createResource(uri2); // HTT: unreachable -- see guard above.
281            writeBeanMap(o2, (Resource)n, typeName); // HTT: unreachable -- see guard above.
282         } else { // HTT: the residual "goto" jump instruction that would exit the if-block above is unreachable -- see guard above; every actual invocation takes this else branch directly.
283            var m2 = (Map)o;
284            n = m.createResource();
285            writeMap(m2, (Resource)n, sType);
286         }
287
288      } else if (sType.isCollectionOrArray() || (nn(wType) && wType.isCollection())) { // HTT: the "nn(wType) && wType.isCollection()" fallback is suspected structurally dead for the same reason as the isMap() fallback above -- the only concrete Delegate collection wrapper in the codebase, DelegateList (internal.DelegateList#getBeanInfo()), always returns a COLLECTION-category ClassMeta from getBeanInfo(), so sType stays isCollectionOrArray()==true and the left disjunct is always what's satisfied; nn(wType)==true is therefore never actually evaluated at this specific check (short-circuited away). Flagged, not deleted.
289
290         var c = sort(sType.isCollection() ? (Collection)o : toList(sType.inner(), o));
291         var f = getCollectionFormat();
292         var cRdf = getRdfClassMeta(sType);
293         var bpRdf = getRdfBeanPropertyMeta(bpm);
294
295         if (cRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT)
296            f = cRdf.getCollectionFormat();
297         if (bpRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT)
298            f = bpRdf.getCollectionFormat();
299
300         if (f == RdfCollectionFormat.MULTI_VALUED) {
301            writeToMultiProperties(c, eType, bpm, attrName, parentResource);
302         } else {
303            n = switch (f) {
304               case BAG -> writeToContainer(c, eType, m.createBag());
305               case LIST -> writeToList(c, eType);
306               default -> writeToContainer(c, eType, m.createSeq());
307            };
308         }
309
310      } else if (sType.isCharSequence() || sType.isChar()) {
311         n = m.createLiteral(encodeTextInvalidChars(o));
312
313      } else if (sType.isNumber() || sType.isBoolean()) {
314         if (! isAddLiteralTypes())
315            n = m.createLiteral(o.toString());
316         else
317            n = m.createTypedLiteral(o);
318
319      } else if (sType.isReader()) {
320         n = m.createLiteral(encodeTextInvalidChars(read((Reader)o, SerializerSession::handleThrown)));
321      } else if (sType.isInputStream()) {
322         n = m.createLiteral(encodeTextInvalidChars(read((InputStream)o, SerializerSession::handleThrown)));
323
324      } else {
325         n = m.createLiteral(encodeTextInvalidChars(toString(o)));
326      }
327
328      pop();
329
330      return n;
331   }
332
333   private void writeBeanMap(BeanMap<?> m, Resource r, String typeName) throws SerializeException {
334      var l = new ArrayList<BeanPropertyValue>();
335
336      if (nn(typeName)) {
337         var pm = m.getMeta().getTypeProperty();
338         l.add(new BeanPropertyValue(pm, pm.getName(), typeName, null));
339      }
340
341      var checkNull = (Predicate<Object>)(x -> isKeepNullProperties() || nn(x));
342      m.forEachValue(checkNull, (pMeta, key, value, thrown) -> l.add(new BeanPropertyValue(pMeta, key, value, thrown)));
343
344      Collections.reverse(l);
345      l.forEach(x -> {
346         var bpMeta = x.getMeta();
347         var cMeta = (ClassMeta<?>) bpMeta.getBeanInfo();
348         var bpRdf = getRdfBeanPropertyMeta(bpMeta);
349         var bpXml = getXmlBeanPropertyMeta(bpMeta);
350
351         if (bpRdf.isBeanUri())
352            return;
353
354         var key = x.getName();
355         var value = x.getValue();
356         var t = x.getThrown();
357         if (nn(t))
358            onBeanGetterException(bpMeta, t); // HTT: onBeanGetterException always throws when reached (isIgnoreInvocationExceptionsOnGetters() is false whenever thrown is actually populated), so JaCoCo cannot register full instruction coverage past a call that never returns; same pattern as CborSerializerSession/HjsonSerializerSession.
359
360         if (canIgnoreValue(bpMeta, key, value))
361            return;
362
363         var ns = bpRdf.getNamespace();
364         if (ns == null && isUseXmlNamespaces())
365            ns = bpXml.getNamespace();
366         if (ns == null)
367            ns = getJuneauBpNs();
368         else if (isAutoDetectNamespaces())
369            addModelPrefix(ns);
370
371         var p = model.createProperty(ns.getUri(), encodeElementName(key));
372         var n = writeAnything(value, bpMeta.isUri(), cMeta, key, bpMeta, r);
373         if (nn(n))
374            r.addProperty(p, n);
375      });
376   }
377
378   private void writeMap(Map m, Resource r, ClassMeta<?> type) throws SerializeException {
379
380      m = sort(m);
381
382      var keyType = type.getKeyType();
383      var valueType = type.getValueType();
384
385      List<Map.Entry<Object,Object>> l = CollectionUtils.toList(m.entrySet());
386      Collections.reverse(l);
387      l.forEach(x -> {
388         Object value = x.getValue();
389         Object key = generalize(x.getKey(), keyType);
390         Namespace ns = getJuneauBpNs();
391         Property p = model.createProperty(ns.getUri(), encodeElementName(toString(key)));
392         RDFNode n = writeAnything(value, false, valueType, toString(key), null, r);
393         if (nn(n)) // HTT: writeMap always passes bpm=null to writeAnything, and writeAnything's null-value branch unconditionally assigns n=RDF_NIL when bpm is null (regardless of keepNullProperties) -- so n is never null here; the false side is only reachable when bpm != null (see writeBeanMap's equivalent check). Flagged as suspected structurally-dead code in this call path, not fixed.
394            r.addProperty(p, n);
395      });
396   }
397
398   private Container writeToContainer(Collection c, ClassMeta<?> type, Container list) throws SerializeException {
399      var elementType = type.getElementType();
400      c.forEach(x -> list.add(writeAnything(x, false, elementType, null, null, null)));
401      return list;
402   }
403
404   private RDFList writeToList(Collection c, ClassMeta<?> type) throws SerializeException {
405      var elementType = type.getElementType();
406      List<RDFNode> l = listOfSize(c.size());
407      c.forEach(x -> l.add(writeAnything(x, false, elementType, null, null, null)));
408      return model.createList(l.iterator());
409   }
410
411   private void writeToMultiProperties(Collection c, ClassMeta<?> sType, BeanPropertyMeta bpm, String attrName, Resource parentResource) throws SerializeException {
412
413      var elementType = sType.getElementType();
414      RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpm);
415      XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(bpm);
416
417      c.forEach(x -> {
418         var ns = bpRdf.getNamespace();
419         if (ns == null && isUseXmlNamespaces())
420            ns = bpXml.getNamespace();
421         if (ns == null)
422            ns = getJuneauBpNs();
423         else if (isAutoDetectNamespaces())
424            addModelPrefix(ns);
425         RDFNode n2 = writeAnything(x, false, elementType, null, null, null);
426         Property p = model.createProperty(ns.getUri(), encodeElementName(attrName));
427         parentResource.addProperty(p, n2);
428      });
429   }
430
431   @SuppressWarnings({
432      "deprecation", // RDFWriter.output(Writer) - RDF uses UTF-8
433      "resource" // Resource management handled by Serializer
434   })
435   @Override /* Overridden from Serializer */
436   protected void doWrite(SerializerPipe out, Object o) throws SerializeException {
437
438      var cm = getClassMetaForObject(o);
439      if (isLooseCollections() && nn(cm) && cm.isCollectionOrArray()) {
440         Collection c = cm.isCollection() ? (Collection)o : toList(cm.inner(), o);
441         forEachEntry(c, x -> writeAnything(x, false, object(), "root", null, null));
442      } else {
443         RDFNode n = writeAnything(o, false, getExpectedRootType(o), "root", null, null);
444         Resource r;
445         if (n.isLiteral()) {
446            r = model.createResource();
447            r.addProperty(pValue, n);
448         } else {
449            r = n.asResource();
450         }
451
452         if (isAddRootProp())
453            r.addProperty(pRoot, "true");
454      }
455
456      // Use RDFWriter - build() returns RDFWriter which has output(Writer)
457      org.apache.jena.riot.RDFWriter.create()
458         .source(model)
459         .lang(lang)
460         .build()
461         .output(out.getWriter());
462   }
463
464   /**
465    * RDF format for representing collections and arrays.
466    *
467    * @see RdfSerializer.Builder#collectionFormat(RdfCollectionFormat)
468    * @return
469    *    RDF format for representing collections and arrays.
470    */
471   protected final RdfCollectionFormat getCollectionFormat() { return ctx.getCollectionFormat(); }
472
473   /**
474    * All Jena-related configuration properties.
475    *
476    * @return
477    *    A map of all Jena-related configuration properties.
478    */
479   protected final Map<String,Object> getJenaSettings() { return ctx.getJenaSettings(); }
480
481   /**
482    * Default XML namespace for bean properties.
483    *
484    * @see RdfSerializer.Builder#juneauBpNs(Namespace)
485    * @return
486    *    The XML namespace to use for bean properties.
487    */
488   protected final Namespace getJuneauBpNs() { return ctx.getJuneauBpNs(); }
489
490   /**
491    * XML namespace for Juneau properties.
492    *
493    * @see RdfSerializer.Builder#juneauNs(Namespace)
494    * @return
495    *    The XML namespace to use for Juneau properties.
496    */
497   protected final Namespace getJuneauNs() { return ctx.getJuneauNs(); }
498
499   /**
500    * RDF language.
501    *
502    * @see RdfSerializer.Builder#language(String)
503    * @return
504    *    The RDF language to use.
505    */
506   protected final String getLanguage() { return ctx.getLanguage(); }
507
508   /**
509    * Default namespaces.
510    *
511    * @see RdfSerializer.Builder#namespaces(Namespace...)
512    * @return
513    *    The default list of namespaces associated with this serializer.
514    */
515   protected final Namespace[] getNamespaces() { return ctx.getNamespaces(); }
516
517   /**
518    * Returns the language-specific metadata on the specified bean.
519    *
520    * @param bm The bean to return the metadata on.
521    * @return The metadata.
522    */
523   protected RdfBeanMeta getRdfBeanMeta(BeanMeta<?> bm) {
524      return ctx.getRdfBeanMeta(bm);
525   }
526
527   /**
528    * Returns the language-specific metadata on the specified bean property.
529    *
530    * @param bpm The bean property to return the metadata on.
531    * @return The metadata.
532    */
533   protected RdfBeanPropertyMeta getRdfBeanPropertyMeta(BeanPropertyMeta bpm) {
534      return bpm == null ? RdfBeanPropertyMeta.DEFAULT : ctx.getRdfBeanPropertyMeta(bpm);
535   }
536
537   /**
538    * Returns the language-specific metadata on the specified class.
539    *
540    * @param cm The class to return the metadata on.
541    * @return The metadata.
542    */
543   protected RdfClassMeta getRdfClassMeta(ClassMeta<?> cm) {
544      return ctx.getRdfClassMeta(cm);
545   }
546
547   /**
548    * Returns the language-specific metadata on the specified bean property.
549    *
550    * @param bpm The bean property to return the metadata on.
551    * @return The metadata.
552    */
553   protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
554      return bpm == null ? XmlBeanPropertyMeta.DEFAULT : ctx.getXmlBeanPropertyMeta(bpm);
555   }
556
557   /**
558    * Add XSI data types to non-<c>String</c> literals.
559    *
560    * @see RdfSerializer.Builder#addLiteralTypes()
561    * @return
562    *    <jk>true</jk> if XSI data types should be added to string literals.
563    */
564   protected final boolean isAddLiteralTypes() { return ctx.isAddLiteralTypes(); }
565
566   /**
567    * Add RDF root identifier property to root node.
568    *
569    * @see RdfSerializer.Builder#addRootProperty()
570    * @return
571    *    <jk>true</jk> if RDF property <c>http://www.apache.org/juneau/root</c> is added with a value of <js>"true"</js>
572    *    to identify the root node in the graph.
573    */
574   protected final boolean isAddRootProp() { return ctx.isAddRootProp(); }
575
576   /**
577    * Auto-detect namespace usage.
578    *
579    * @see RdfSerializer.Builder#disableAutoDetectNamespaces()
580    * @return
581    *    <jk>true</jk> if namespaces usage should be detected before serialization.
582    */
583   protected final boolean isAutoDetectNamespaces() { return ctx.isAutoDetectNamespaces(); }
584
585   /**
586    * Collections should be serialized and parsed as loose collections.
587    *
588    * @see RdfSerializer.Builder#looseCollections()
589    * @return
590    *    <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of
591    *    resources that are children of an RDF collection (e.g. Sequence, Bag).
592    */
593   protected final boolean isLooseCollections() { return ctx.isLooseCollections(); }
594
595   /**
596    * Reuse XML namespaces when RDF namespaces not specified.
597    *
598    * @see RdfSerializer.Builder#disableUseXmlNamespaces()
599    * @return
600    *    <jk>true</jk> if namespaces defined using {@link XmlNs @XmlNs} and {@link Xml @Xml} will be inherited by the RDF serializers.
601    *    <br>Otherwise, namespaces will be defined using {@link RdfNs @RdfNs} and {@link Rdf @Rdf}.
602    */
603   protected final boolean isUseXmlNamespaces() { return ctx.isUseXmlNamespaces(); }
604}