blob: 2036305ba5eb36dd195bb4a4c948f908c445a677 [file] [log] [blame]
// ***************************************************************************************************************************
// * 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.juneau.jena;
import static org.apache.juneau.jena.Constants.*;
import static org.apache.juneau.jena.RdfSerializer.*;
import java.io.IOException;
import java.util.*;
import org.apache.jena.rdf.model.*;
import org.apache.juneau.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.jena.annotation.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.xml.*;
import org.apache.juneau.xml.annotation.*;
/**
* Session object that lives for the duration of a single use of {@link RdfSerializer}.
*
* <p>
* This class is NOT thread safe.
* It is typically discarded after one-time use although it can be reused within the same thread.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class RdfSerializerSession extends WriterSerializerSession {
private final RdfSerializer ctx;
private final Property pRoot, pValue;
private final Model model;
private final RDFWriter writer;
private final Namespace[] namespaces;
/**
* Create a new session using properties specified in the context.
*
* @param ctx
* The context creating this session object.
* The context contains all the configuration settings for this object.
* @param args
* Runtime arguments.
* These specify session-level information such as locale and URI context.
* It also include session-level properties that override the properties defined on the bean and
* serializer contexts.
*/
protected RdfSerializerSession(RdfSerializer ctx, SerializerSessionArgs args) {
super(ctx, args);
this.ctx = ctx;
namespaces = getInstanceArrayProperty(RDF_namespaces, Namespace.class, ctx.namespaces);
model = ModelFactory.createDefaultModel();
addModelPrefix(ctx.getJuneauNs());
addModelPrefix(ctx.getJuneauBpNs());
for (Namespace ns : this.namespaces)
addModelPrefix(ns);
pRoot = model.createProperty(ctx.getJuneauNs().getUri(), RDF_juneauNs_ROOT);
pValue = model.createProperty(ctx.getJuneauNs().getUri(), RDF_juneauNs_VALUE);
writer = model.getWriter(ctx.getLanguage());
// Only apply properties with this prefix!
String propPrefix = RdfCommon.LANG_PROP_MAP.get(ctx.getLanguage());
if (propPrefix == null)
throw new FormattedRuntimeException("Unknown RDF language encountered: ''{0}''", ctx.getLanguage());
// RDF/XML specific properties.
if (propPrefix.equals("rdfXml.")) {
writer.setProperty("tab", isUseWhitespace() ? 2 : 0);
writer.setProperty("attributeQuoteChar", Character.toString(getQuoteChar()));
}
for (Map.Entry<String,Object> e : ctx.jenaProperties.entrySet())
if (e.getKey().startsWith(propPrefix, 5))
writer.setProperty(e.getKey().substring(5 + propPrefix.length()), e.getValue());
for (String k : getPropertyKeys())
if (k.startsWith("RdfCommon.jena.") && k.startsWith(propPrefix, 15))
writer.setProperty(k.substring(15 + propPrefix.length()), getProperty(k));
}
/*
* Adds the specified namespace as a model prefix.
*/
private void addModelPrefix(Namespace ns) {
model.setNsPrefix(ns.getName(), ns.getUri());
}
/*
* XML-encodes the specified string using the {@link XmlUtils#escapeText(Object)} method.
*/
private String encodeTextInvalidChars(Object o) {
if (o == null)
return null;
String s = toString(o);
return XmlUtils.escapeText(s);
}
/*
* XML-encoded the specified element name using the {@link XmlUtils#encodeElementName(Object)} method.
*/
private String encodeElementName(Object o) {
return XmlUtils.encodeElementName(toString(o));
}
@Override /* Serializer */
protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
Resource r = null;
ClassMeta<?> cm = getClassMetaForObject(o);
if (isLooseCollections() && cm != null && cm.isCollectionOrArray()) {
Collection c = sort(cm.isCollection() ? (Collection)o : toList(cm.getInnerClass(), o));
for (Object o2 : c)
serializeAnything(o2, false, object(), "root", null, null);
} else {
RDFNode n = serializeAnything(o, false, getExpectedRootType(o), "root", null, null);
if (n.isLiteral()) {
r = model.createResource();
r.addProperty(pValue, n);
} else {
r = n.asResource();
}
if (isAddRootProp())
r.addProperty(pRoot, "true");
}
writer.write(model, out.getWriter(), "http://unknown/");
}
private RDFNode serializeAnything(Object o, boolean isURI, ClassMeta<?> eType,
String attrName, BeanPropertyMeta bpm, Resource parentResource) throws IOException, SerializeException {
Model m = model;
ClassMeta<?> aType = null; // The actual type
ClassMeta<?> wType = null; // The wrapped type
ClassMeta<?> sType = object(); // The serialized type
aType = push2(attrName, o, eType);
if (eType == null)
eType = object();
// Handle recursion
if (aType == null) {
o = null;
aType = object();
}
// Handle Optional<X>
if (isOptional(aType)) {
o = getOptionalValue(o);
eType = getOptionalType(eType);
aType = getClassMetaForObject(o, object());
}
if (o != null) {
if (aType.isDelegate()) {
wType = aType;
aType = ((Delegate)o).getClassMeta();
}
sType = aType;
// Swap if necessary
PojoSwap swap = aType.getPojoSwap(this);
if (swap != null) {
o = swap(swap, o);
sType = swap.getSwapClassMeta(this);
// If the getSwapClass() method returns Object, we need to figure out
// the actual type now.
if (sType.isObject())
sType = getClassMetaForObject(o);
}
} else {
sType = eType.getSerializedClassMeta(this);
}
String typeName = getBeanTypeName(eType, aType, bpm);
RDFNode n = null;
if (o == null || sType.isChar() && ((Character)o).charValue() == 0) {
if (bpm != null) {
if (! isTrimNullProperties()) {
n = m.createResource(RDF_NIL);
}
} else {
n = m.createResource(RDF_NIL);
}
} else if (sType.isUri() || isURI) {
// Note that RDF URIs must be absolute to be valid!
String uri = getUri(o, null);
if (StringUtils.isAbsoluteUri(uri))
n = m.createResource(uri);
else
n = m.createLiteral(encodeTextInvalidChars(uri));
} else if (sType.isCharSequence() || sType.isChar()) {
n = m.createLiteral(encodeTextInvalidChars(o));
} else if (sType.isNumber() || sType.isBoolean()) {
if (! isAddLiteralTypes())
n = m.createLiteral(o.toString());
else
n = m.createTypedLiteral(o);
} else if (sType.isMap() || (wType != null && wType.isMap())) {
if (o instanceof BeanMap) {
BeanMap bm = (BeanMap)o;
Object uri = null;
RdfBeanMeta rbm = bRdf(bm.getMeta());
if (rbm.hasBeanUri())
uri = rbm.getBeanUriProperty().get(bm, null);
String uri2 = getUri(uri, null);
n = m.createResource(uri2);
serializeBeanMap(bm, (Resource)n, typeName);
} else {
Map m2 = (Map)o;
n = m.createResource();
serializeMap(m2, (Resource)n, sType);
}
} else if (sType.isBean()) {
BeanMap bm = toBeanMap(o);
Object uri = null;
RdfBeanMeta rbm = bRdf(bm.getMeta());
if (rbm.hasBeanUri())
uri = rbm.getBeanUriProperty().get(bm, null);
String uri2 = getUri(uri, null);
n = m.createResource(uri2);
serializeBeanMap(bm, (Resource)n, typeName);
} else if (sType.isCollectionOrArray() || (wType != null && wType.isCollection())) {
Collection c = sort(sType.isCollection() ? (Collection)o : toList(sType.getInnerClass(), o));
RdfCollectionFormat f = getCollectionFormat();
RdfClassMeta cRdf = cRdf(sType);
RdfBeanPropertyMeta bpRdf = bpRdf(bpm);
if (cRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT)
f = cRdf.getCollectionFormat();
if (bpRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT)
f = bpRdf.getCollectionFormat();
switch (f) {
case BAG: n = serializeToContainer(c, eType, m.createBag()); break;
case LIST: n = serializeToList(c, eType); break;
case MULTI_VALUED: serializeToMultiProperties(c, eType, bpm, attrName, parentResource); break;
default: n = serializeToContainer(c, eType, m.createSeq());
}
} else if (sType.isReader() || sType.isInputStream()) {
n = m.createLiteral(encodeTextInvalidChars(IOUtils.read(o)));
} else {
n = m.createLiteral(encodeTextInvalidChars(toString(o)));
}
pop();
return n;
}
private String getUri(Object uri, Object uri2) {
String s = null;
if (uri != null)
s = uri.toString();
if ((s == null || s.isEmpty()) && uri2 != null)
s = uri2.toString();
if (s == null)
return null;
return getUriResolver().resolve(s);
}
private void serializeMap(Map m, Resource r, ClassMeta<?> type) throws IOException, SerializeException {
m = sort(m);
ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
ArrayList<Map.Entry<Object,Object>> l = new ArrayList<>(m.entrySet());
Collections.reverse(l);
for (Map.Entry<Object,Object> me : l) {
Object value = me.getValue();
Object key = generalize(me.getKey(), keyType);
Namespace ns = getJuneauBpNs();
Property p = model.createProperty(ns.getUri(), encodeElementName(toString(key)));
RDFNode n = serializeAnything(value, false, valueType, toString(key), null, r);
if (n != null)
r.addProperty(p, n);
}
}
private void serializeBeanMap(BeanMap<?> m, Resource r, String typeName) throws IOException, SerializeException {
List<BeanPropertyValue> l = m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null);
Collections.reverse(l);
for (BeanPropertyValue bpv : l) {
BeanPropertyMeta bpMeta = bpv.getMeta();
ClassMeta<?> cMeta = bpMeta.getClassMeta();
RdfBeanPropertyMeta bpRdf = bpRdf(bpMeta);
XmlBeanPropertyMeta bpXml = bpXml(bpMeta);
if (bpRdf.isBeanUri())
continue;
String key = bpv.getName();
Object value = bpv.getValue();
Throwable t = bpv.getThrown();
if (t != null)
onBeanGetterException(bpMeta, t);
if (canIgnoreValue(cMeta, key, value))
continue;
Namespace ns = bpRdf.getNamespace();
if (ns == null && isUseXmlNamespaces())
ns = bpXml.getNamespace();
if (ns == null)
ns = getJuneauBpNs();
else if (isAutoDetectNamespaces())
addModelPrefix(ns);
Property p = model.createProperty(ns.getUri(), encodeElementName(key));
RDFNode n = serializeAnything(value, bpMeta.isUri(), cMeta, key, bpMeta, r);
if (n != null)
r.addProperty(p, n);
}
}
private Container serializeToContainer(Collection c, ClassMeta<?> type, Container list) throws IOException, SerializeException {
ClassMeta<?> elementType = type.getElementType();
for (Object e : c) {
RDFNode n = serializeAnything(e, false, elementType, null, null, null);
list = list.add(n);
}
return list;
}
private RDFList serializeToList(Collection c, ClassMeta<?> type) throws IOException, SerializeException {
ClassMeta<?> elementType = type.getElementType();
List<RDFNode> l = new ArrayList<>(c.size());
for (Object e : c) {
l.add(serializeAnything(e, false, elementType, null, null, null));
}
return model.createList(l.iterator());
}
private void serializeToMultiProperties(Collection c, ClassMeta<?> sType,
BeanPropertyMeta bpm, String attrName, Resource parentResource) throws IOException, SerializeException {
ClassMeta<?> elementType = sType.getElementType();
RdfBeanPropertyMeta bpRdf = bpRdf(bpm);
XmlBeanPropertyMeta bpXml = bpXml(bpm);
for (Object e : c) {
Namespace ns = bpRdf.getNamespace();
if (ns == null && isUseXmlNamespaces())
ns = bpXml.getNamespace();
if (ns == null)
ns = getJuneauBpNs();
else if (isAutoDetectNamespaces())
addModelPrefix(ns);
RDFNode n2 = serializeAnything(e, false, elementType, null, null, null);
Property p = model.createProperty(ns.getUri(), encodeElementName(attrName));
parentResource.addProperty(p, n2);
}
}
private static RdfClassMeta cRdf(ClassMeta<?> cm) {
return cm.getExtendedMeta(RdfClassMeta.class);
}
private static XmlBeanPropertyMeta bpXml(BeanPropertyMeta pMeta) {
return pMeta == null ? XmlBeanPropertyMeta.DEFAULT : pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
}
private static RdfBeanPropertyMeta bpRdf(BeanPropertyMeta pMeta) {
return pMeta == null ? RdfBeanPropertyMeta.DEFAULT : pMeta.getExtendedMeta(RdfBeanPropertyMeta.class);
}
private static RdfBeanMeta bRdf(BeanMeta bm) {
return (RdfBeanMeta)bm.getExtendedMeta(RdfBeanMeta.class);
}
//-----------------------------------------------------------------------------------------------------------------
// Common properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: RDF format for representing collections and arrays.
*
* @see RdfSerializer#RDF_collectionFormat
* @return
* RDF format for representing collections and arrays.
*/
protected final RdfCollectionFormat getCollectionFormat() {
return ctx.getCollectionFormat();
}
/**
* Configuration property: Default XML namespace for bean properties.
*
* @see RdfSerializer#RDF_juneauBpNs
* @return
* The XML namespace to use for bean properties.
*/
protected final Namespace getJuneauBpNs() {
return ctx.getJuneauBpNs();
}
/**
* Configuration property: XML namespace for Juneau properties.
*
* @see RdfSerializer#RDF_juneauNs
* @return
* The XML namespace to use for Juneau properties.
*/
protected final Namespace getJuneauNs() {
return ctx.getJuneauNs();
}
/**
* Configuration property: RDF language.
*
* @see RdfSerializer#RDF_language
* @return
* The RDF language to use.
*/
protected final String getLanguage() {
return ctx.getLanguage();
}
/**
* Configuration property: Collections should be serialized and parsed as loose collections.
*
* @see RdfSerializer#RDF_looseCollections
* @return
* <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of
* resources that are children of an RDF collection (e.g. Sequence, Bag).
*/
protected final boolean isLooseCollections() {
return ctx.isLooseCollections();
}
//-----------------------------------------------------------------------------------------------------------------
// Jena properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: All Jena-related configuration properties.
*
* @return
* A map of all Jena-related configuration properties.
*/
protected final Map<String,Object> getJenaProperties() {
return ctx.getJenaProperties();
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: Add <js>"_type"</js> properties when needed.
*
* @see RdfSerializer#RDF_addBeanTypes
* @return
* <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
* through reflection.
*/
@Override
protected final boolean isAddBeanTypes() {
return ctx.isAddBeanTypes();
}
/**
* Configuration property: Add XSI data types to non-<c>String</c> literals.
*
* @see RdfSerializer#RDF_addLiteralTypes
* @return
* <jk>true</jk> if XSI data types should be added to string literals.
*/
protected final boolean isAddLiteralTypes() {
return ctx.isAddLiteralTypes();
}
/**
* Configuration property: Add RDF root identifier property to root node.
*
* @see RdfSerializer#RDF_addRootProperty
* @return
* <jk>true</jk> if RDF property <c>http://www.apache.org/juneau/root</c> is added with a value of <js>"true"</js>
* to identify the root node in the graph.
*/
protected final boolean isAddRootProp() {
return ctx.isAddRootProp();
}
/**
* Configuration property: Auto-detect namespace usage.
*
* @see RdfSerializer#RDF_autoDetectNamespaces
* @return
* <jk>true</jk> if namespaces usage should be detected before serialization.
*/
protected final boolean isAutoDetectNamespaces() {
return ctx.isAutoDetectNamespaces();
}
/**
* Configuration property: Default namespaces.
*
* @see RdfSerializer#RDF_namespaces
* @return
* The default list of namespaces associated with this serializer.
*/
protected final Namespace[] getNamespaces() {
return ctx.getNamespaces();
}
/**
* Configuration property: Reuse XML namespaces when RDF namespaces not specified.
*
* @see RdfSerializer#RDF_useXmlNamespaces
* @return
* <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.
* <br>Otherwise, namespaces will be defined using {@link RdfNs @RdfNs} and {@link Rdf @Rdf}.
*/
protected final boolean isUseXmlNamespaces() {
return ctx.isUseXmlNamespaces();
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Session */
public ObjectMap toMap() {
return super.toMap()
.append("RdfSerializerSession", new DefaultFilteringObjectMap()
);
}
}