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.jena.RdfSerializer.*;
017
018import java.io.IOException;
019import java.util.*;
020
021import org.apache.jena.rdf.model.*;
022import org.apache.juneau.*;
023import org.apache.juneau.internal.*;
024import org.apache.juneau.jena.annotation.*;
025import org.apache.juneau.serializer.*;
026import org.apache.juneau.transform.*;
027import org.apache.juneau.xml.*;
028import org.apache.juneau.xml.annotation.*;
029
030/**
031 * Session object that lives for the duration of a single use of {@link RdfSerializer}.
032 *
033 * <p>
034 * This class is NOT thread safe.
035 * It is typically discarded after one-time use although it can be reused within the same thread.
036 */
037@SuppressWarnings({ "rawtypes", "unchecked" })
038public final class RdfSerializerSession extends WriterSerializerSession {
039
040   private final RdfSerializer ctx;
041   private final Property pRoot, pValue;
042   private final Model model;
043   private final RDFWriter writer;
044   private final Namespace[] namespaces;
045
046   /**
047    * Create a new session using properties specified in the context.
048    *
049    * @param ctx
050    *    The context creating this session object.
051    *    The context contains all the configuration settings for this object.
052    * @param args
053    *    Runtime arguments.
054    *    These specify session-level information such as locale and URI context.
055    *    It also include session-level properties that override the properties defined on the bean and
056    *    serializer contexts.
057    */
058   protected RdfSerializerSession(RdfSerializer ctx, SerializerSessionArgs args) {
059      super(ctx, args);
060      this.ctx = ctx;
061
062      namespaces = getInstanceArrayProperty(RDF_namespaces, Namespace.class, ctx.namespaces);
063      model = ModelFactory.createDefaultModel();
064      addModelPrefix(ctx.getJuneauNs());
065      addModelPrefix(ctx.getJuneauBpNs());
066      for (Namespace ns : this.namespaces)
067         addModelPrefix(ns);
068      pRoot = model.createProperty(ctx.getJuneauNs().getUri(), RDF_juneauNs_ROOT);
069      pValue = model.createProperty(ctx.getJuneauNs().getUri(), RDF_juneauNs_VALUE);
070      writer = model.getWriter(ctx.getLanguage());
071
072      // Only apply properties with this prefix!
073      String propPrefix = RdfCommon.LANG_PROP_MAP.get(ctx.getLanguage());
074      if (propPrefix == null)
075         throw new FormattedRuntimeException("Unknown RDF language encountered: ''{0}''", ctx.getLanguage());
076
077      // RDF/XML specific properties.
078      if (propPrefix.equals("rdfXml.")) {
079         writer.setProperty("tab", isUseWhitespace() ? 2 : 0);
080         writer.setProperty("attributeQuoteChar", Character.toString(getQuoteChar()));
081      }
082
083      for (Map.Entry<String,Object> e : ctx.jenaProperties.entrySet())
084         if (e.getKey().startsWith(propPrefix, 5))
085            writer.setProperty(e.getKey().substring(5 + propPrefix.length()), e.getValue());
086
087      for (String k : getPropertyKeys())
088         if (k.startsWith("RdfCommon.jena.") && k.startsWith(propPrefix, 15))
089            writer.setProperty(k.substring(15 + propPrefix.length()), getProperty(k));
090   }
091
092   /*
093    * Adds the specified namespace as a model prefix.
094    */
095   private void addModelPrefix(Namespace ns) {
096      model.setNsPrefix(ns.getName(), ns.getUri());
097   }
098
099   /*
100    * XML-encodes the specified string using the {@link XmlUtils#escapeText(Object)} method.
101    */
102   private String encodeTextInvalidChars(Object o) {
103      if (o == null)
104         return null;
105      String s = toString(o);
106      return XmlUtils.escapeText(s);
107   }
108
109   /*
110    * XML-encoded the specified element name using the {@link XmlUtils#encodeElementName(Object)} method.
111    */
112   private String encodeElementName(Object o) {
113      return XmlUtils.encodeElementName(toString(o));
114   }
115
116   @Override /* Serializer */
117   protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
118
119      Resource r = null;
120
121      ClassMeta<?> cm = getClassMetaForObject(o);
122      if (isLooseCollections() && cm != null && cm.isCollectionOrArray()) {
123         Collection c = sort(cm.isCollection() ? (Collection)o : toList(cm.getInnerClass(), o));
124         for (Object o2 : c)
125            serializeAnything(o2, false, object(), "root", null, null);
126      } else {
127         RDFNode n = serializeAnything(o, false, getExpectedRootType(o), "root", null, null);
128         if (n.isLiteral()) {
129            r = model.createResource();
130            r.addProperty(pValue, n);
131         } else {
132            r = n.asResource();
133         }
134
135         if (isAddRootProp())
136            r.addProperty(pRoot, "true");
137      }
138
139      writer.write(model, out.getWriter(), "http://unknown/");
140   }
141
142   private RDFNode serializeAnything(Object o, boolean isURI, ClassMeta<?> eType,
143         String attrName, BeanPropertyMeta bpm, Resource parentResource) throws IOException, SerializeException {
144      Model m = model;
145
146      ClassMeta<?> aType = null;       // The actual type
147      ClassMeta<?> wType = null;       // The wrapped type
148      ClassMeta<?> sType = object();   // The serialized type
149
150      aType = push2(attrName, o, eType);
151
152      if (eType == null)
153         eType = object();
154
155      // Handle recursion
156      if (aType == null) {
157         o = null;
158         aType = object();
159      }
160
161      // Handle Optional<X>
162      if (isOptional(aType)) {
163         o = getOptionalValue(o);
164         eType = getOptionalType(eType);
165         aType = getClassMetaForObject(o, object());
166      }
167
168      if (o != null) {
169
170         if (aType.isDelegate()) {
171            wType = aType;
172            aType = ((Delegate)o).getClassMeta();
173         }
174
175         sType = aType;
176
177         // Swap if necessary
178         PojoSwap swap = aType.getPojoSwap(this);
179         if (swap != null) {
180            o = swap(swap, o);
181            sType = swap.getSwapClassMeta(this);
182
183            // If the getSwapClass() method returns Object, we need to figure out
184            // the actual type now.
185            if (sType.isObject())
186               sType = getClassMetaForObject(o);
187         }
188      } else {
189         sType = eType.getSerializedClassMeta(this);
190      }
191
192      String typeName = getBeanTypeName(eType, aType, bpm);
193
194      RDFNode n = null;
195
196      if (o == null || sType.isChar() && ((Character)o).charValue() == 0) {
197         if (bpm != null) {
198            if (! isTrimNullProperties()) {
199               n = m.createResource(RDF_NIL);
200            }
201         } else {
202            n = m.createResource(RDF_NIL);
203         }
204
205      } else if (sType.isUri() || isURI) {
206         // Note that RDF URIs must be absolute to be valid!
207         String uri = getUri(o, null);
208         if (StringUtils.isAbsoluteUri(uri))
209            n = m.createResource(uri);
210         else
211            n = m.createLiteral(encodeTextInvalidChars(uri));
212
213      } else if (sType.isCharSequence() || sType.isChar()) {
214         n = m.createLiteral(encodeTextInvalidChars(o));
215
216      } else if (sType.isNumber() || sType.isBoolean()) {
217         if (! isAddLiteralTypes())
218            n = m.createLiteral(o.toString());
219         else
220            n = m.createTypedLiteral(o);
221
222      } else if (sType.isMap() || (wType != null && wType.isMap())) {
223         if (o instanceof BeanMap) {
224            BeanMap bm = (BeanMap)o;
225            Object uri = null;
226            RdfBeanMeta rbm = getRdfBeanMeta(bm.getMeta());
227            if (rbm.hasBeanUri())
228               uri = rbm.getBeanUriProperty().get(bm, null);
229            String uri2 = getUri(uri, null);
230            n = m.createResource(uri2);
231            serializeBeanMap(bm, (Resource)n, typeName);
232         } else {
233            Map m2 = (Map)o;
234            n = m.createResource();
235            serializeMap(m2, (Resource)n, sType);
236         }
237
238      } else if (sType.isBean()) {
239         BeanMap bm = toBeanMap(o);
240         Object uri = null;
241         RdfBeanMeta rbm = getRdfBeanMeta(bm.getMeta());
242         if (rbm.hasBeanUri())
243            uri = rbm.getBeanUriProperty().get(bm, null);
244         String uri2 = getUri(uri, null);
245         n = m.createResource(uri2);
246         serializeBeanMap(bm, (Resource)n, typeName);
247
248      } else if (sType.isCollectionOrArray() || (wType != null && wType.isCollection())) {
249
250         Collection c = sort(sType.isCollection() ? (Collection)o : toList(sType.getInnerClass(), o));
251         RdfCollectionFormat f = getCollectionFormat();
252         RdfClassMeta cRdf = getRdfClassMeta(sType);
253         RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpm);
254
255         if (cRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT)
256            f = cRdf.getCollectionFormat();
257         if (bpRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT)
258            f = bpRdf.getCollectionFormat();
259
260         switch (f) {
261            case BAG: n = serializeToContainer(c, eType, m.createBag()); break;
262            case LIST: n = serializeToList(c, eType); break;
263            case MULTI_VALUED: serializeToMultiProperties(c, eType, bpm, attrName, parentResource); break;
264            default: n = serializeToContainer(c, eType, m.createSeq());
265         }
266
267      } else if (sType.isReader() || sType.isInputStream()) {
268         n = m.createLiteral(encodeTextInvalidChars(IOUtils.read(o)));
269
270      } else {
271         n = m.createLiteral(encodeTextInvalidChars(toString(o)));
272      }
273
274      pop();
275
276      return n;
277   }
278
279   private String getUri(Object uri, Object uri2) {
280      String s = null;
281      if (uri != null)
282         s = uri.toString();
283      if ((s == null || s.isEmpty()) && uri2 != null)
284         s = uri2.toString();
285      if (s == null)
286         return null;
287      return getUriResolver().resolve(s);
288   }
289
290   private void serializeMap(Map m, Resource r, ClassMeta<?> type) throws IOException, SerializeException {
291
292      m = sort(m);
293
294      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
295
296      ArrayList<Map.Entry<Object,Object>> l = new ArrayList<>(m.entrySet());
297      Collections.reverse(l);
298      for (Map.Entry<Object,Object> me : l) {
299         Object value = me.getValue();
300
301         Object key = generalize(me.getKey(), keyType);
302
303         Namespace ns = getJuneauBpNs();
304         Property p = model.createProperty(ns.getUri(), encodeElementName(toString(key)));
305         RDFNode n = serializeAnything(value, false, valueType, toString(key), null, r);
306         if (n != null)
307            r.addProperty(p, n);
308      }
309   }
310
311   private void serializeBeanMap(BeanMap<?> m, Resource r, String typeName) throws IOException, SerializeException {
312      List<BeanPropertyValue> l = m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null);
313      Collections.reverse(l);
314      for (BeanPropertyValue bpv : l) {
315
316         BeanPropertyMeta bpMeta = bpv.getMeta();
317         ClassMeta<?> cMeta = bpMeta.getClassMeta();
318         RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpMeta);
319         XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(bpMeta);
320
321         if (bpRdf.isBeanUri())
322            continue;
323
324         String key = bpv.getName();
325         Object value = bpv.getValue();
326         Throwable t = bpv.getThrown();
327         if (t != null)
328            onBeanGetterException(bpMeta, t);
329
330         if (canIgnoreValue(cMeta, key, value))
331            continue;
332
333         Namespace ns = bpRdf.getNamespace();
334         if (ns == null && isUseXmlNamespaces())
335            ns = bpXml.getNamespace();
336         if (ns == null)
337            ns = getJuneauBpNs();
338         else if (isAutoDetectNamespaces())
339            addModelPrefix(ns);
340
341         Property p = model.createProperty(ns.getUri(), encodeElementName(key));
342         RDFNode n = serializeAnything(value, bpMeta.isUri(), cMeta, key, bpMeta, r);
343         if (n != null)
344            r.addProperty(p, n);
345      }
346   }
347
348
349   private Container serializeToContainer(Collection c, ClassMeta<?> type, Container list) throws IOException, SerializeException {
350
351      ClassMeta<?> elementType = type.getElementType();
352      for (Object e : c) {
353         RDFNode n = serializeAnything(e, false, elementType, null, null, null);
354         list = list.add(n);
355      }
356      return list;
357   }
358
359   private RDFList serializeToList(Collection c, ClassMeta<?> type) throws IOException, SerializeException {
360      ClassMeta<?> elementType = type.getElementType();
361      List<RDFNode> l = new ArrayList<>(c.size());
362      for (Object e : c) {
363         l.add(serializeAnything(e, false, elementType, null, null, null));
364      }
365      return model.createList(l.iterator());
366   }
367
368   private void serializeToMultiProperties(Collection c, ClassMeta<?> sType,
369         BeanPropertyMeta bpm, String attrName, Resource parentResource) throws IOException, SerializeException {
370
371      ClassMeta<?> elementType = sType.getElementType();
372      RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpm);
373      XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(bpm);
374
375      for (Object e : c) {
376         Namespace ns = bpRdf.getNamespace();
377         if (ns == null && isUseXmlNamespaces())
378            ns = bpXml.getNamespace();
379         if (ns == null)
380            ns = getJuneauBpNs();
381         else if (isAutoDetectNamespaces())
382            addModelPrefix(ns);
383         RDFNode n2 = serializeAnything(e, false, elementType, null, null, null);
384         Property p = model.createProperty(ns.getUri(), encodeElementName(attrName));
385         parentResource.addProperty(p, n2);
386      }
387   }
388
389   
390   //-----------------------------------------------------------------------------------------------------------------
391   // Common properties
392   //-----------------------------------------------------------------------------------------------------------------
393
394   /**
395    * Configuration property:  RDF format for representing collections and arrays.
396    *
397    * @see RdfSerializer#RDF_collectionFormat
398    * @return
399    *    RDF format for representing collections and arrays.
400    */
401   protected final RdfCollectionFormat getCollectionFormat() {
402      return ctx.getCollectionFormat();
403   }
404
405   /**
406    * Configuration property:  Default XML namespace for bean properties.
407    *
408    * @see RdfSerializer#RDF_juneauBpNs
409    * @return
410    *    The XML namespace to use for bean properties.
411    */
412   protected final Namespace getJuneauBpNs() {
413      return ctx.getJuneauBpNs();
414   }
415
416   /**
417    * Configuration property:  XML namespace for Juneau properties.
418    *
419    * @see RdfSerializer#RDF_juneauNs
420    * @return
421    *    The XML namespace to use for Juneau properties.
422    */
423   protected final Namespace getJuneauNs() {
424      return ctx.getJuneauNs();
425   }
426
427   /**
428    * Configuration property:  RDF language.
429    *
430    * @see RdfSerializer#RDF_language
431    * @return
432    *    The RDF language to use.
433    */
434   protected final String getLanguage() {
435      return ctx.getLanguage();
436   }
437
438   /**
439    * Configuration property:  Collections should be serialized and parsed as loose collections.
440    *
441    * @see RdfSerializer#RDF_looseCollections
442    * @return
443    *    <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of
444    *    resources that are children of an RDF collection (e.g. Sequence, Bag).
445    */
446   protected final boolean isLooseCollections() {
447      return ctx.isLooseCollections();
448   }
449
450   //-----------------------------------------------------------------------------------------------------------------
451   // Jena properties
452   //-----------------------------------------------------------------------------------------------------------------
453
454   /**
455    * Configuration property:  All Jena-related configuration properties.
456    *
457    * @return
458    *    A map of all Jena-related configuration properties.
459    */
460   protected final Map<String,Object> getJenaProperties() {
461      return ctx.getJenaProperties();
462   }
463
464   //-----------------------------------------------------------------------------------------------------------------
465   // Properties
466   //-----------------------------------------------------------------------------------------------------------------
467
468   /**
469    * Configuration property:  Add <js>"_type"</js> properties when needed.
470    *
471    * @see RdfSerializer#RDF_addBeanTypes
472    * @return
473    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
474    *    through reflection.
475    */
476   @Override
477   protected final boolean isAddBeanTypes() {
478      return ctx.isAddBeanTypes();
479   }
480
481   /**
482    * Configuration property:  Add XSI data types to non-<c>String</c> literals.
483    *
484    * @see RdfSerializer#RDF_addLiteralTypes
485    * @return
486    *    <jk>true</jk> if XSI data types should be added to string literals.
487    */
488   protected final boolean isAddLiteralTypes() {
489      return ctx.isAddLiteralTypes();
490   }
491
492   /**
493    * Configuration property:  Add RDF root identifier property to root node.
494    *
495    * @see RdfSerializer#RDF_addRootProperty
496    * @return
497    *    <jk>true</jk> if RDF property <c>http://www.apache.org/juneau/root</c> is added with a value of <js>"true"</js>
498    *    to identify the root node in the graph.
499    */
500   protected final boolean isAddRootProp() {
501      return ctx.isAddRootProp();
502   }
503
504   /**
505    * Configuration property:  Auto-detect namespace usage.
506    *
507    * @see RdfSerializer#RDF_autoDetectNamespaces
508    * @return
509    *    <jk>true</jk> if namespaces usage should be detected before serialization.
510    */
511   protected final boolean isAutoDetectNamespaces() {
512      return ctx.isAutoDetectNamespaces();
513   }
514
515   /**
516    * Configuration property:  Default namespaces.
517    *
518    * @see RdfSerializer#RDF_namespaces
519    * @return
520    *    The default list of namespaces associated with this serializer.
521    */
522   protected final Namespace[] getNamespaces() {
523      return ctx.getNamespaces();
524   }
525
526   /**
527    * Configuration property:  Reuse XML namespaces when RDF namespaces not specified.
528    *
529    * @see RdfSerializer#RDF_useXmlNamespaces
530    * @return
531    *    <jk>true</jk> if namespaces defined using {@link XmlNs @XmlNs} and {@link org.apache.juneau.xml.annotation.Xml @Xml} will be inherited by the RDF serializers.
532    *    <br>Otherwise, namespaces will be defined using {@link RdfNs @RdfNs} and {@link Rdf @Rdf}.
533    */
534   protected final boolean isUseXmlNamespaces() {
535      return ctx.isUseXmlNamespaces();
536   }
537
538   //-----------------------------------------------------------------------------------------------------------------
539   // Extended metadata
540   //-----------------------------------------------------------------------------------------------------------------
541
542   /**
543    * Returns the language-specific metadata on the specified class.
544    *
545    * @param cm The class to return the metadata on.
546    * @return The metadata.
547    */
548   protected RdfClassMeta getRdfClassMeta(ClassMeta<?> cm) {
549      return ctx.getRdfClassMeta(cm);
550   }
551
552   /**
553    * Returns the language-specific metadata on the specified bean.
554    *
555    * @param bm The bean to return the metadata on.
556    * @return The metadata.
557    */
558   protected RdfBeanMeta getRdfBeanMeta(BeanMeta<?> bm) {
559      return ctx.getRdfBeanMeta(bm);
560   }
561
562   /**
563    * Returns the language-specific metadata on the specified bean property.
564    *
565    * @param bpm The bean property to return the metadata on.
566    * @return The metadata.
567    */
568   protected RdfBeanPropertyMeta getRdfBeanPropertyMeta(BeanPropertyMeta bpm) {
569      return bpm == null ? RdfBeanPropertyMeta.DEFAULT : ctx.getRdfBeanPropertyMeta(bpm);
570   }
571
572   /**
573    * Returns the language-specific metadata on the specified bean property.
574    *
575    * @param bpm The bean property to return the metadata on.
576    * @return The metadata.
577    */
578   protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
579      return bpm == null ? XmlBeanPropertyMeta.DEFAULT : ctx.getXmlBeanPropertyMeta(bpm);
580   }
581
582   //-----------------------------------------------------------------------------------------------------------------
583   // Other methods
584   //-----------------------------------------------------------------------------------------------------------------
585
586   @Override /* Session */
587   public ObjectMap toMap() {
588      return super.toMap()
589         .append("RdfSerializerSession", new DefaultFilteringObjectMap()
590         );
591   }
592}