001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.jena;
014
015import static org.apache.juneau.internal.CollectionUtils.*;
016
017import java.util.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.jena.annotation.*;
021import org.apache.juneau.serializer.*;
022import org.apache.juneau.xml.*;
023import org.apache.juneau.xml.annotation.*;
024
025/**
026 * Serializes POJOs to RDF.
027 *
028 * <h5 class='topic'>Behavior-specific subclasses</h5>
029 *
030 * The following direct subclasses are provided for language-specific serializers:
031 * <ul>
032 *    <li>{@link RdfXmlSerializer} - RDF/XML.
033 *    <li>{@link RdfXmlAbbrevSerializer} - RDF/XML-ABBREV.
034 *    <li>{@link NTripleSerializer} - N-TRIPLE.
035 *    <li>{@link TurtleSerializer} - TURTLE.
036 *    <li>{@link N3Serializer} - N3.
037 * </ul>
038 *
039 * <h5 class='section'>See Also:</h5>
040 * <ul>
041 *    <li class='link'>{@doc juneau-marshall-rdf}
042 * </ul>
043 */
044public class RdfSerializer extends WriterSerializer implements RdfCommon {
045
046   private static final Namespace
047      DEFAULT_JUNEAU_NS = Namespace.create("j", "http://www.apache.org/juneau/"),
048      DEFAULT_JUNEAUBP_NS = Namespace.create("jp", "http://www.apache.org/juneaubp/");
049
050   //-------------------------------------------------------------------------------------------------------------------
051   // Configurable properties
052   //-------------------------------------------------------------------------------------------------------------------
053
054   private static final String PREFIX = "RdfSerializer.";
055
056   /**
057    * Configuration property:  Add <js>"_type"</js> properties when needed.
058    *
059    * <h5 class='section'>Property:</h5>
060    * <ul>
061    *    <li><b>Name:</b>  <js>"RdfSerializer.addBeanTypes.b"</js>
062    *    <li><b>Data type:</b>  <code>Boolean</code>
063    *    <li><b>Default:</b>  <jk>false</jk>
064    *    <li><b>Session property:</b>  <jk>false</jk>
065    *    <li><b>Methods:</b>
066    *       <ul>
067    *          <li class='jm'>{@link RdfSerializerBuilder#addBeanTypes(boolean)}
068    *       </ul>
069    * </ul>
070    *
071    * <h5 class='section'>Description:</h5>
072    * <p>
073    * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
074    * through reflection.
075    *
076    * <p>
077    * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is
078    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
079    */
080   public static final String RDF_addBeanTypes = PREFIX + "addBeanTypes.b";
081
082   /**
083    * Configuration property:  Add XSI data types to non-<code>String</code> literals.
084    *
085    * <h5 class='section'>Property:</h5>
086    * <ul>
087    *    <li><b>Name:</b>  <js>"RdfSerializer.addLiteralTypes.b"</js>
088    *    <li><b>Data type:</b>  <code>Boolean</code>
089    *    <li><b>Default:</b>  <jk>false</jk>
090    *    <li><b>Session property:</b>  <jk>false</jk>
091    *    <li><b>Methods:</b>
092    *       <ul>
093    *          <li class='jm'>{@link RdfSerializerBuilder#addLiteralTypes(boolean)}
094    *          <li class='jm'>{@link RdfSerializerBuilder#addLiteralTypes()}
095    *       </ul>
096    * </ul>
097    */
098   public static final String RDF_addLiteralTypes = PREFIX + "addLiteralTypes.b";
099
100   /**
101    * Configuration property:  Add RDF root identifier property to root node.
102    *
103    * <h5 class='section'>Property:</h5>
104    * <ul>
105    *    <li><b>Name:</b>  <js>"RdfSerializer.addRootProperty.b"</js>
106    *    <li><b>Data type:</b>  <code>Boolean</code>
107    *    <li><b>Default:</b>  <jk>false</jk>
108    *    <li><b>Session property:</b>  <jk>false</jk>
109    *    <li><b>Methods:</b>
110    *       <ul>
111    *          <li class='jm'>{@link RdfSerializerBuilder#addRootProperty(boolean)}
112    *          <li class='jm'>{@link RdfSerializerBuilder#addRootProperty()}
113    *       </ul>
114    * </ul>
115    *
116    * <h5 class='section'>Description:</h5>
117    * <p>
118    * When enabled an RDF property <code>http://www.apache.org/juneau/root</code> is added with a value of <js>"true"</js>
119    * to identify the root node in the graph.
120    * <br>This helps locate the root node during parsing.
121    *
122    * <p>
123    * If disabled, the parser has to search through the model to find any resources without incoming predicates to
124    * identify root notes, which can introduce a considerable performance degradation.
125    */
126   public static final String RDF_addRootProperty = PREFIX + "addRootProperty.b";
127
128   /**
129    * Configuration property:  Auto-detect namespace usage.
130    *
131    * <h5 class='section'>Property:</h5>
132    * <ul>
133    *    <li><b>Name:</b>  <js>"RdfSerializer.autoDetectNamespaces.b"</js>
134    *    <li><b>Data type:</b>  <code>Boolean</code>
135    *    <li><b>Default:</b>  <jk>true</jk>
136    *    <li><b>Session property:</b>  <jk>false</jk>
137    *    <li><b>Methods:</b>
138    *       <ul>
139    *          <li class='jm'>{@link RdfSerializerBuilder#autoDetectNamespaces(boolean)}
140    *       </ul>
141    * </ul>
142    *
143    * <h5 class='section'>Description:</h5>
144    * <p>
145    * Detect namespace usage before serialization.
146    *
147    * <p>
148    * If enabled, then the data structure will first be crawled looking for namespaces that will be encountered before
149    * the root element is serialized.
150    */
151   public static final String RDF_autoDetectNamespaces = PREFIX + "autoDetectNamespaces.b";
152
153   /**
154    * Configuration property:  Default namespaces.
155    *
156    * <h5 class='section'>Property:</h5>
157    * <ul>
158    *    <li><b>Name:</b>  <js>"RdfSerializer.namespaces.ls"</js>
159    *    <li><b>Data type:</b>  <code>List&lt;String&gt;</code> (serialized {@link Namespace} objects)
160    *    <li><b>Default:</b>  empty list
161    *    <li><b>Session property:</b>  <jk>false</jk>
162    *    <li><b>Annotations:</b>
163    *       <ul>
164    *          <li class='ja'>{@link Rdf#namespace()}
165    *       </ul>
166    *    <li><b>Methods:</b>
167    *       <ul>
168    *          <li class='jm'>{@link RdfSerializerBuilder#namespaces(Namespace...)}
169    *       </ul>
170    * </ul>
171    *
172    * <h5 class='section'>Description:</h5>
173    * <p>
174    * The default list of namespaces associated with this serializer.
175    */
176   public static final String RDF_namespaces = PREFIX + "namespaces.ls";
177
178   //-------------------------------------------------------------------------------------------------------------------
179   // Instance
180   //-------------------------------------------------------------------------------------------------------------------
181
182   private final boolean
183      addLiteralTypes,
184      addRootProperty,
185      useXmlNamespaces,
186      looseCollections,
187      autoDetectNamespaces,
188      addBeanTypes;
189   private final String rdfLanguage;
190   private final Namespace juneauNs;
191   private final Namespace juneauBpNs;
192   private final RdfCollectionFormat collectionFormat;
193   final Map<String,Object> jenaSettings;
194   final Namespace[] namespaces;
195
196   /**
197    * Constructor.
198    *
199    * @param ps
200    *    The property store containing all the settings for this object.
201    * @param produces
202    *    The media type that this serializer produces.
203    * @param accept
204    *    The accept media types that the serializer can handle.
205    *    <p>
206    *    Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1}
207    *    <p>
208    *    If empty, then assumes the only media type supported is <code>produces</code>.
209    *    <p>
210    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
211    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
212    *    <p class='bcode w800'>
213    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
214    *    </p>
215    *    <br>...or...
216    *    <p class='bcode w800'>
217    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
218    *    </p>
219    * <p>
220    * The accept value can also contain q-values.
221    */
222   public RdfSerializer(PropertyStore ps, String produces, String accept) {
223      super(ps, produces, accept);
224      addLiteralTypes = getBooleanProperty(RDF_addLiteralTypes, false);
225      addRootProperty = getBooleanProperty(RDF_addRootProperty, false);
226      useXmlNamespaces = getBooleanProperty(RDF_useXmlNamespaces, true);
227      looseCollections = getBooleanProperty(RDF_looseCollections, false);
228      autoDetectNamespaces = getBooleanProperty(RDF_autoDetectNamespaces, true);
229      rdfLanguage = getStringProperty(RDF_language, "RDF/XML-ABBREV");
230      juneauNs = getProperty(RDF_juneauNs, Namespace.class, DEFAULT_JUNEAU_NS);
231      juneauBpNs = getProperty(RDF_juneauBpNs, Namespace.class, DEFAULT_JUNEAUBP_NS);
232      collectionFormat = getProperty(RDF_collectionFormat, RdfCollectionFormat.class, RdfCollectionFormat.DEFAULT);
233      namespaces = getProperty(RDF_namespaces, Namespace[].class, new Namespace[0]);
234      addBeanTypes = getBooleanProperty(RDF_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
235
236      Map<String,Object> m = new LinkedHashMap<>();
237      for (String k : getPropertyKeys("RdfCommon"))
238         if (k.startsWith("jena."))
239            m.put(k.substring(5), getProperty(k));
240      jenaSettings = unmodifiableMap(m);
241   }
242
243   /**
244    * Constructor.
245    *
246    * @param ps
247    *    The property store containing all the settings for this object.
248    */
249   public RdfSerializer(PropertyStore ps) {
250      this(ps, "text/xml+rdf", (String)null);
251   }
252
253
254   @Override /* Context */
255   public RdfSerializerBuilder builder() {
256      return new RdfSerializerBuilder(getPropertyStore());
257   }
258
259   /**
260    * Instantiates a new clean-slate {@link RdfSerializerBuilder} object.
261    *
262    * <p>
263    * This is equivalent to simply calling <code><jk>new</jk> RdfSerializerBuilder()</code>.
264    *
265    * <p>
266    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
267    * the settings of the object called on.
268    *
269    * @return A new {@link RdfSerializerBuilder} object.
270    */
271   public static RdfSerializerBuilder create() {
272      return new RdfSerializerBuilder();
273   }
274
275   @Override /* Serializer */
276   public WriterSerializerSession createSession(SerializerSessionArgs args) {
277      return new RdfSerializerSession(this, args);
278   }
279
280   //-----------------------------------------------------------------------------------------------------------------
281   // Properties
282   //-----------------------------------------------------------------------------------------------------------------
283
284   /**
285    * Configuration property:  Add XSI data types to non-<code>String</code> literals.
286    *
287    * @see #RDF_addLiteralTypes
288    * @return
289    *    <jk>true</jk> if XSI data types should be added to string literals.
290    */
291   protected final boolean isAddLiteralTypes() {
292      return addLiteralTypes;
293   }
294
295   /**
296    * Configuration property:  Add RDF root identifier property to root node.
297    *
298    * @see #RDF_addRootProperty
299    * @return
300    *    <jk>true</jk> if RDF property <code>http://www.apache.org/juneau/root</code> is added with a value of <js>"true"</js>
301    *    to identify the root node in the graph.
302    */
303   protected final boolean isAddRootProp() {
304      return addRootProperty;
305   }
306
307   /**
308    * Configuration property:  Reuse XML namespaces when RDF namespaces not specified.
309    *
310    * @see #RDF_useXmlNamespaces
311    * @return
312    *    <jk>true</jk> if namespaces defined using {@link XmlNs @XmlNs} and {@link Xml @Xml} will be inherited by the RDF serializers.
313    *    <br>Otherwise, namespaces will be defined using {@link RdfNs @RdfNs} and {@link Rdf @Rdf}.
314    */
315   protected final boolean isUseXmlNamespaces() {
316      return useXmlNamespaces;
317   }
318
319   /**
320    * Configuration property:  Collections should be serialized and parsed as loose collections.
321    *
322    * @see #RDF_looseCollections
323    * @return
324    *    <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of
325    *    resources that are children of an RDF collection (e.g. Sequence, Bag).
326    */
327   protected final boolean isLooseCollections() {
328      return looseCollections;
329   }
330
331   /**
332    * Configuration property:  Auto-detect namespace usage.
333    *
334    * @see #RDF_autoDetectNamespaces
335    * @return
336    *    <jk>true</jk> if namespaces usage should be detected before serialization.
337    */
338   protected final boolean isAutoDetectNamespaces() {
339      return autoDetectNamespaces;
340   }
341
342   /**
343    * Configuration property:  Add <js>"_type"</js> properties when needed.
344    *
345    * @see #RDF_addBeanTypes
346    * @return
347    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
348    *    through reflection.
349    */
350   @Override
351   protected final boolean isAddBeanTypes() {
352      return addBeanTypes;
353   }
354
355   /**
356    * Configuration property:  RDF language.
357    *
358    * @see #RDF_language
359    * @return
360    *    The RDF language to use.
361    */
362   protected final String getRdfLanguage() {
363      return rdfLanguage;
364   }
365
366   /**
367    * Configuration property:  XML namespace for Juneau properties.
368    *
369    * @see #RDF_juneauNs
370    * @return
371    *    The XML namespace to use for Juneau properties.
372    */
373   protected final Namespace getJuneauNs() {
374      return juneauNs;
375   }
376
377   /**
378    * Configuration property:  Default XML namespace for bean properties.
379    *
380    * @see #RDF_juneauBpNs
381    * @return
382    *    The XML namespace to use for bean properties.
383    */
384   protected final Namespace getJuneauBpNs() {
385      return juneauBpNs;
386   }
387
388   /**
389    * Configuration property:  RDF format for representing collections and arrays.
390    *
391    * @see #RDF_collectionFormat
392    * @return
393    *    RDF format for representing collections and arrays.
394    */
395   protected final RdfCollectionFormat getCollectionFormat() {
396      return collectionFormat;
397   }
398
399   @Override /* Context */
400   public ObjectMap asMap() {
401      return super.asMap()
402         .append("RdfSerializer", new ObjectMap()
403            .append("addLiteralTypes", addLiteralTypes)
404            .append("addRootProperty", addRootProperty)
405            .append("useXmlNamespaces", useXmlNamespaces)
406            .append("looseCollections", looseCollections)
407            .append("autoDetectNamespaces", autoDetectNamespaces)
408            .append("rdfLanguage", rdfLanguage)
409            .append("juneauNs", juneauNs)
410            .append("juneauBpNs", juneauBpNs)
411            .append("collectionFormat", collectionFormat)
412            .append("namespaces", namespaces)
413            .append("addBeanTypes", addBeanTypes)
414         );
415   }
416}