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.jena.Constants.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017
018import java.util.*;
019
020import org.apache.juneau.*;
021import org.apache.juneau.jena.annotation.*;
022import org.apache.juneau.serializer.*;
023import org.apache.juneau.xml.*;
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 RdfSerializer.Xml} - RDF/XML.
033 *    <li>{@link RdfSerializer.XmlAbbrev} - RDF/XML-ABBREV.
034 *    <li>{@link RdfSerializer.NTriple} - N-TRIPLE.
035 *    <li>{@link RdfSerializer.Turtle} - TURTLE.
036 *    <li>{@link RdfSerializer.N3} - N3.
037 * </ul>
038 * 
039 * <h5 class='section'>See Also:</h5>
040 * <ul>
041 *    <li class='link'><a class="doclink" href="package-summary.html#TOC">org.apache.juneau.jena &gt; RDF Overview</a>
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.addBeanTypeProperties.b"</js>
062    *    <li><b>Data type:</b>  <code>Boolean</code>
063    *    <li><b>Default:</b>  <jk>false</jk>
064    *    <li><b>Session-overridable:</b>  <jk>true</jk>
065    *    <li><b>Methods:</b> 
066    *       <ul>
067    *          <li class='jm'>{@link RdfSerializerBuilder#addBeanTypeProperties(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_addBeanTypeProperties} setting and is
078    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
079    */
080   public static final String RDF_addBeanTypeProperties = PREFIX + "addBeanTypeProperties.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-overridable:</b>  <jk>true</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-overridable:</b>  <jk>true</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-overridable:</b>  <jk>true</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-overridable:</b>  <jk>true</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   //-------------------------------------------------------------------------------------------------------------------
180   // Predefined instances
181   //-------------------------------------------------------------------------------------------------------------------
182
183   /** Default RDF/XML serializer, all default settings.*/
184   public static final RdfSerializer DEFAULT_XML = new Xml(PropertyStore.DEFAULT);
185
186   /** Default Abbreviated RDF/XML serializer, all default settings.*/
187   public static final RdfSerializer DEFAULT_XMLABBREV = new XmlAbbrev(PropertyStore.DEFAULT);
188
189   /** Default Turtle serializer, all default settings.*/
190   public static final RdfSerializer DEFAULT_TURTLE = new Turtle(PropertyStore.DEFAULT);
191
192   /** Default N-Triple serializer, all default settings.*/
193   public static final RdfSerializer DEFAULT_NTRIPLE = new NTriple(PropertyStore.DEFAULT);
194
195   /** Default N3 serializer, all default settings.*/
196   public static final RdfSerializer DEFAULT_N3 = new N3(PropertyStore.DEFAULT);
197
198
199   //-------------------------------------------------------------------------------------------------------------------
200   // Predefined subclasses
201   //-------------------------------------------------------------------------------------------------------------------
202
203   /** Produces RDF/XML output */
204   public static class Xml extends RdfSerializer {
205
206      /**
207       * Constructor.
208       * 
209       * @param ps The property store containing all the settings for this object.
210       */
211      public Xml(PropertyStore ps) {
212         super(
213            ps.builder()
214               .set(RDF_language, LANG_RDF_XML)
215               .build(), 
216            "text/xml+rdf"
217         );
218      }
219   }
220
221   /** Produces Abbreviated RDF/XML output */
222   public static class XmlAbbrev extends RdfSerializer {
223
224      /**
225       * Constructor.
226       * 
227       * @param ps The property store containing all the settings for this object.
228       */
229      public XmlAbbrev(PropertyStore ps) {
230         super(
231            ps.builder()
232               .set(RDF_language, LANG_RDF_XML_ABBREV)
233               .build(), 
234            "text/xml+rdf", 
235            "text/xml+rdf+abbrev"
236         );
237      }
238   }
239
240   /** Produces N-Triple output */
241   public static class NTriple extends RdfSerializer {
242
243      /**
244       * Constructor.
245       * 
246       * @param ps The property store containing all the settings for this object.
247       */
248      public NTriple(PropertyStore ps) {
249         super(
250            ps.builder()
251               .set(RDF_language, LANG_NTRIPLE)
252               .build(), 
253            "text/n-triple"
254         );
255      }
256   }
257
258   /** Produces Turtle output */
259   public static class Turtle extends RdfSerializer {
260
261      /**
262       * Constructor.
263       * 
264       * @param ps The property store containing all the settings for this object.
265       */
266      public Turtle(PropertyStore ps) {
267         super(
268            ps.builder()
269               .set(RDF_language, LANG_TURTLE)
270               .build(), 
271            "text/turtle"
272         );
273      }
274   }
275
276   /** Produces N3 output */
277   public static class N3 extends RdfSerializer {
278
279      /**
280       * Constructor.
281       * 
282       * @param ps The property store containing all the settings for this object.
283       */
284      public N3(PropertyStore ps) {
285         super(
286            ps.builder()
287               .set(RDF_language, LANG_N3)
288               .build(), 
289            "text/n3"
290         );
291      }
292   }
293
294
295   //-------------------------------------------------------------------------------------------------------------------
296   // Instance
297   //-------------------------------------------------------------------------------------------------------------------
298
299   final boolean
300      addLiteralTypes,
301      addRootProperty,
302      useXmlNamespaces,
303      looseCollections,
304      autoDetectNamespaces,
305      addBeanTypeProperties;
306   final String rdfLanguage;
307   final Namespace juneauNs;
308   final Namespace juneauBpNs;
309   final RdfCollectionFormat collectionFormat;
310   final Map<String,Object> jenaSettings;
311   final Namespace[] namespaces;
312
313   /**
314    * Constructor.
315    * 
316    * @param ps
317    *    The property store containing all the settings for this object.
318    * @param produces
319    *    The media type that this serializer produces.
320    * @param accept
321    *    The accept media types that the serializer can handle.
322    *    <p>
323    *    Can contain meta-characters per the <code>media-type</code> specification of
324    *    <a class="doclink" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">RFC2616/14.1</a>
325    *    <p>
326    *    If empty, then assumes the only media type supported is <code>produces</code>.
327    *    <p>
328    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
329    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
330    *    <p class='bcode'>
331    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json"</js>, <js>"text/json"</js>);
332    *    </p>
333    *    <br>...or...
334    *    <p class='bcode'>
335    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
336    *    </p>
337    */
338   public RdfSerializer(PropertyStore ps, String produces, String...accept) {
339      super(ps, produces, accept);
340      addLiteralTypes = getBooleanProperty(RDF_addLiteralTypes, false);
341      addRootProperty = getBooleanProperty(RDF_addRootProperty, false);
342      useXmlNamespaces = getBooleanProperty(RDF_useXmlNamespaces, true);
343      looseCollections = getBooleanProperty(RDF_looseCollections, false);
344      autoDetectNamespaces = getBooleanProperty(RDF_autoDetectNamespaces, true);
345      rdfLanguage = getStringProperty(RDF_language, "RDF/XML-ABBREV");
346      juneauNs = getProperty(RDF_juneauNs, Namespace.class, DEFAULT_JUNEAU_NS);
347      juneauBpNs = getProperty(RDF_juneauBpNs, Namespace.class, DEFAULT_JUNEAUBP_NS);
348      collectionFormat = getProperty(RDF_collectionFormat, RdfCollectionFormat.class, RdfCollectionFormat.DEFAULT);
349      namespaces = getProperty(RDF_namespaces, Namespace[].class, new Namespace[0]);
350      addBeanTypeProperties = getBooleanProperty(RDF_addBeanTypeProperties, getBooleanProperty(SERIALIZER_addBeanTypeProperties, true));
351      
352      Map<String,Object> m = new LinkedHashMap<>();
353      for (String k : getPropertyKeys("RdfCommon")) 
354         if (k.startsWith("jena."))
355            m.put(k.substring(5), getProperty(k));
356      jenaSettings = unmodifiableMap(m);
357   }
358   
359   /**
360    * Constructor.
361    * 
362    * @param ps
363    *    The property store containing all the settings for this object.
364    */
365   public RdfSerializer(PropertyStore ps) {
366      this(ps, "text/xml+rdf");
367   }
368   
369
370   @Override /* Context */
371   public RdfSerializerBuilder builder() {
372      return new RdfSerializerBuilder(getPropertyStore());
373   }
374
375   /**
376    * Instantiates a new clean-slate {@link RdfSerializerBuilder} object.
377    * 
378    * <p>
379    * This is equivalent to simply calling <code><jk>new</jk> RdfSerializerBuilder()</code>.
380    * 
381    * <p>
382    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 
383    * the settings of the object called on.
384    * 
385    * @return A new {@link RdfSerializerBuilder} object.
386    */
387   public static RdfSerializerBuilder create() {
388      return new RdfSerializerBuilder();
389   }
390
391   @Override /* Serializer */
392   public WriterSerializerSession createSession(SerializerSessionArgs args) {
393      return new RdfSerializerSession(this, args);
394   }
395   
396   @Override /* Context */
397   public ObjectMap asMap() {
398      return super.asMap()
399         .append("RdfSerializer", new ObjectMap()
400            .append("addLiteralTypes", addLiteralTypes)
401            .append("addRootProperty", addRootProperty)
402            .append("useXmlNamespaces", useXmlNamespaces)
403            .append("looseCollections", looseCollections)
404            .append("autoDetectNamespaces", autoDetectNamespaces)
405            .append("rdfLanguage", rdfLanguage)
406            .append("juneauNs", juneauNs)
407            .append("juneauBpNs", juneauBpNs)
408            .append("collectionFormat", collectionFormat)
409            .append("namespaces", namespaces)
410            .append("addBeanTypeProperties", addBeanTypeProperties)
411         );
412   }
413}