001/**
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fcrepo.kernel.impl.rdf.converters;
017
018import com.google.common.base.Converter;
019import com.google.common.base.Splitter;
020import com.hp.hpl.jena.datatypes.BaseDatatype;
021import com.hp.hpl.jena.datatypes.RDFDatatype;
022import com.hp.hpl.jena.rdf.model.Literal;
023import com.hp.hpl.jena.rdf.model.RDFNode;
024import com.hp.hpl.jena.rdf.model.Resource;
025import org.fcrepo.kernel.models.FedoraResource;
026import org.fcrepo.kernel.exception.RepositoryRuntimeException;
027import org.slf4j.Logger;
028
029import javax.jcr.Node;
030import javax.jcr.RepositoryException;
031import javax.jcr.Session;
032import javax.jcr.Value;
033import javax.jcr.ValueFactory;
034
035import java.util.Iterator;
036
037import static com.hp.hpl.jena.rdf.model.ResourceFactory.createLangLiteral;
038import static com.hp.hpl.jena.rdf.model.ResourceFactory.createPlainLiteral;
039import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
040import static com.hp.hpl.jena.rdf.model.ResourceFactory.createTypedLiteral;
041import static javax.jcr.PropertyType.BOOLEAN;
042import static javax.jcr.PropertyType.DATE;
043import static javax.jcr.PropertyType.DECIMAL;
044import static javax.jcr.PropertyType.DOUBLE;
045import static javax.jcr.PropertyType.LONG;
046import static javax.jcr.PropertyType.PATH;
047import static javax.jcr.PropertyType.REFERENCE;
048import static javax.jcr.PropertyType.STRING;
049import static javax.jcr.PropertyType.UNDEFINED;
050import static javax.jcr.PropertyType.URI;
051import static javax.jcr.PropertyType.WEAKREFERENCE;
052import static org.fcrepo.kernel.impl.identifiers.NodeResourceConverter.nodeToResource;
053import static org.slf4j.LoggerFactory.getLogger;
054
055/**
056 * @author cabeer
057 * @since 10/8/14
058 */
059public class ValueConverter extends Converter<Value, RDFNode> {
060
061    private static final Logger LOGGER = getLogger(ValueConverter.class);
062
063    private final Session session;
064    private final Converter<Node, Resource> graphSubjects;
065
066    /**
067     * Convert values between JCR values and RDF objects with the given session and subjects
068     * @param session the session
069     * @param graphSubjects the graph subjects
070     */
071    public ValueConverter(final Session session,
072                          final Converter<Resource, FedoraResource> graphSubjects) {
073        this.session = session;
074        this.graphSubjects = nodeToResource(graphSubjects);
075    }
076
077    @Override
078    protected RDFNode doForward(final Value value) {
079        try {
080            switch (value.getType()) {
081                case BOOLEAN:
082                    return literal2node(value.getBoolean());
083                case DATE:
084                    return literal2node(value.getDate());
085                case DECIMAL:
086                    return literal2node(value.getDecimal());
087                case DOUBLE:
088                    return literal2node(value.getDouble());
089                case LONG:
090                    return literal2node(value.getLong());
091                case URI:
092                    return createResource(value.getString());
093                case REFERENCE:
094                case WEAKREFERENCE:
095                case PATH:
096                    return traverseLink(value);
097                default:
098                    return stringliteral2node(value.getString());
099            }
100        } catch (final RepositoryException e) {
101            throw new RepositoryRuntimeException(e);
102        }
103    }
104
105    @Override
106    protected Value doBackward(final RDFNode resource) {
107
108        try {
109
110            final ValueFactory valueFactory = session.getValueFactory();
111
112            if (resource.isAnon()) {
113                // a non-URI resource (e.g. a blank node)
114                return valueFactory.createValue(resource.toString(), UNDEFINED);
115            }
116
117            final RdfLiteralJcrValueBuilder rdfLiteralJcrValueBuilder = new RdfLiteralJcrValueBuilder();
118
119            if (resource.isURIResource()) {
120                rdfLiteralJcrValueBuilder.value(resource.asResource().getURI()).datatype("URI");
121            } else {
122
123                final Literal literal = resource.asLiteral();
124                final RDFDatatype dataType = literal.getDatatype();
125
126                rdfLiteralJcrValueBuilder.value(literal.getString()).datatype(dataType).lang(literal.getLanguage());
127            }
128
129            return valueFactory.createValue(rdfLiteralJcrValueBuilder.toString(), STRING);
130        } catch (final RepositoryException e) {
131            throw new RepositoryRuntimeException(e);
132        }
133    }
134
135    private static Literal literal2node(final Object literal) {
136        final Literal result = createTypedLiteral(literal);
137        LOGGER.trace("Converting {} into {}", literal, result);
138        return result;
139    }
140
141
142    private static RDFNode stringliteral2node(final String literal) {
143        final RdfLiteralJcrValueBuilder rdfLiteralJcrValueBuilder = new RdfLiteralJcrValueBuilder(literal);
144
145        if (rdfLiteralJcrValueBuilder.hasLang()) {
146            return createLangLiteral(rdfLiteralJcrValueBuilder.value(), rdfLiteralJcrValueBuilder.lang());
147        } else if (rdfLiteralJcrValueBuilder.isResource()) {
148            return createResource(rdfLiteralJcrValueBuilder.value());
149        } else if (rdfLiteralJcrValueBuilder.hasDatatypeUri()) {
150            return createTypedLiteral(rdfLiteralJcrValueBuilder.value(), rdfLiteralJcrValueBuilder.datatype());
151        } else {
152            return createPlainLiteral(literal);
153        }
154    }
155
156    private RDFNode traverseLink(final Value v)
157            throws RepositoryException {
158        final javax.jcr.Node refNode;
159        if (v.getType() == PATH) {
160            refNode = session.getNode(v.getString());
161        } else {
162            refNode = session.getNodeByIdentifier(v.getString());
163        }
164        return getGraphSubject(refNode);
165    }
166
167    private RDFNode getGraphSubject(final javax.jcr.Node n) {
168        return graphSubjects.convert(n);
169    }
170
171    protected static class RdfLiteralJcrValueBuilder {
172        private static final String FIELD_DELIMITER = "\30^^\30";
173        public static final Splitter JCR_VALUE_SPLITTER = Splitter.on(FIELD_DELIMITER);
174
175        private String value;
176        private String datatypeUri;
177        private String lang;
178
179        RdfLiteralJcrValueBuilder() {
180
181        }
182
183        public RdfLiteralJcrValueBuilder(final String literal) {
184            this();
185
186            final Iterator<String> tokenizer = JCR_VALUE_SPLITTER.split(literal).iterator();
187
188            value = tokenizer.next();
189
190            if (tokenizer.hasNext()) {
191                datatypeUri = tokenizer.next();
192            }
193
194            if (tokenizer.hasNext()) {
195                lang = tokenizer.next();
196            }
197        }
198
199        @Override
200        public String toString() {
201            final StringBuilder b = new StringBuilder();
202
203            b.append(value);
204
205            if (hasDatatypeUri()) {
206                b.append(FIELD_DELIMITER);
207                b.append(datatypeUri);
208            } else if (hasLang()) {
209                // if it has a language, but not a datatype, add a placeholder.
210                b.append(FIELD_DELIMITER);
211            }
212
213            if (hasLang()) {
214                b.append(FIELD_DELIMITER);
215                b.append(lang);
216            }
217
218            return b.toString();
219
220        }
221
222        public String value() {
223            return value;
224        }
225
226        public RDFDatatype datatype() {
227            if (hasDatatypeUri()) {
228                return new BaseDatatype(datatypeUri);
229            }
230            return null;
231        }
232
233        public String lang() {
234            return lang;
235        }
236
237        public RdfLiteralJcrValueBuilder value(final String value) {
238            this.value = value;
239            return this;
240        }
241
242        public RdfLiteralJcrValueBuilder datatype(final String datatypeUri) {
243            this.datatypeUri = datatypeUri;
244            return this;
245        }
246
247        public RdfLiteralJcrValueBuilder datatype(final RDFDatatype datatypeUri) {
248            if (datatypeUri != null && !datatypeUri.getURI().isEmpty()) {
249                this.datatypeUri = datatypeUri.getURI();
250            }
251            return this;
252        }
253
254        public RdfLiteralJcrValueBuilder lang(final String lang) {
255            this.lang = lang;
256            return this;
257        }
258
259        public boolean hasLang() {
260            return lang != null && !lang.isEmpty();
261        }
262
263        public boolean hasDatatypeUri() {
264            return datatypeUri != null && !datatypeUri.isEmpty();
265        }
266
267        public boolean isResource() {
268            return hasDatatypeUri() && datatypeUri.equals("URI");
269        }
270    }
271}