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.impl.mappings;
017
018import static com.google.common.base.Throwables.propagate;
019import static com.hp.hpl.jena.graph.NodeFactory.createLiteral;
020import static com.hp.hpl.jena.graph.Triple.create;
021import static org.fcrepo.kernel.impl.identifiers.NodeResourceConverter.nodeToResource;
022import static org.slf4j.LoggerFactory.getLogger;
023
024import java.util.Iterator;
025
026import javax.jcr.Node;
027import javax.jcr.Property;
028import javax.jcr.RepositoryException;
029import javax.jcr.Session;
030import javax.jcr.Value;
031
032import com.google.common.base.Converter;
033import com.google.common.collect.Iterators;
034import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
035import com.hp.hpl.jena.graph.impl.LiteralLabel;
036import com.hp.hpl.jena.rdf.model.Resource;
037import org.fcrepo.kernel.models.FedoraResource;
038import org.fcrepo.kernel.impl.rdf.converters.PropertyConverter;
039import org.fcrepo.kernel.impl.rdf.converters.ValueConverter;
040import org.slf4j.Logger;
041import com.google.common.base.Function;
042import com.hp.hpl.jena.graph.Triple;
043
044/**
045 * Utility for moving from JCR properties to RDF triples.
046 *
047 * @author ajs6f
048 * @since Oct 10, 2013
049 */
050public class PropertyToTriple implements
051        Function<Property, Iterator<Triple>> {
052
053    private static final PropertyConverter propertyConverter = new PropertyConverter();
054    private final ValueConverter valueConverter;
055    private Converter<Node, Resource> graphSubjects;
056
057    private static final Logger LOGGER = getLogger(PropertyToTriple.class);
058
059    /**
060     * Default constructor. We require a {@link Converter} in order to
061     * construct the externally-meaningful RDF subjects of our triples.
062     *
063     * @param graphSubjects the graph subjects
064     * @param session the session
065     */
066    public PropertyToTriple(final Session session, final Converter<Resource, FedoraResource> graphSubjects) {
067        this.valueConverter = new ValueConverter(session, graphSubjects);
068        this.graphSubjects = nodeToResource(graphSubjects);
069    }
070
071    /**
072     * This nightmare of Java signature verbosity is a curried transformation.
073     * We want to go from an iterator of JCR {@link Property} to an iterator
074     * of RDF {@link Triple}s. An annoyance: some properties may produce several
075     * triples (multi-valued properties). So we cannot find a simple Property to
076     * Triple mapping. Instead, we wax clever and offer a function from any
077     * specific property to a new function, one that takes multiple values (such
078     * as occur in our multi-valued properties) to multiple triples. In other
079     * words, this is a function the outputs of which are functions specific to
080     * a given JCR property. Each output knows how to take any specific value of
081     * its specific property to a triple representing the fact that its specific
082     * property obtains that specific value on the node to which that property
083     * belongs. All of this is useful because with these operations represented
084     * as functions instead of ordinary methods, which may have side-effects, we
085     * can use efficient machinery to manipulate iterators of the objects in
086     * which we are interested, and that's exactly what we want to do in this
087     * class. See {@link org.fcrepo.kernel.impl.rdf.impl.PropertiesRdfContext#triplesFromProperties} for an
088     * example of the use of this class with ZippingIterator.
089     *
090     * @see <a href="http://en.wikipedia.org/wiki/Currying">Currying</a>
091     */
092    @Override
093    public Iterator<Triple> apply(final Property p) {
094        return Iterators.transform(new PropertyValueIterator(p), new Function<Value, Triple>() {
095
096            @Override
097            public Triple apply(final Value v) {
098                return propertyvalue2triple(p, v);
099            }
100        });
101    }
102
103    /**
104     * @param p A JCR {@link Property}
105     * @param v The {@link Value} of that Property to use (in the case of
106     *        multi-valued properties)  For single valued properties this
107     *        must be that single value.
108     * @return An RDF {@link Triple} representing that property.
109     */
110    private Triple propertyvalue2triple(final Property p, final Value v) {
111        LOGGER.trace("Rendering triple for Property: {} with Value: {}", p, v);
112        try {
113
114            final Triple triple = create(graphSubjects.convert(p.getParent()).asNode(),
115                    propertyConverter.convert(p).asNode(),
116                    convertObject(p, v));
117
118            LOGGER.trace("Created triple: {} ", triple);
119            return triple;
120        } catch (final RepositoryException e) {
121            throw propagate(e);
122        }
123    }
124
125    private com.hp.hpl.jena.graph.Node convertObject(final Property p, final Value v) throws RepositoryException {
126        final com.hp.hpl.jena.graph.Node object = valueConverter.convert(v).asNode();
127
128        if (object.isLiteral()) {
129            final String propertyName = p.getName();
130            final int i = propertyName.indexOf('@');
131
132            if (i > 0) {
133                final LiteralLabel literal = object.getLiteral();
134                final String datatypeURI = literal.getDatatypeURI();
135
136                if (datatypeURI.isEmpty() || datatypeURI.equals(XSDDatatype.XSDstring.getURI())) {
137
138                    final String lang = propertyName.substring(i + 1);
139                    return createLiteral(literal.getLexicalForm(), lang, literal.getDatatype());
140                }
141            }
142        }
143
144        return object;
145    }
146
147}