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
064     */
065    public PropertyToTriple(final Session session, final Converter<Resource, FedoraResource> graphSubjects) {
066        this.valueConverter = new ValueConverter(session, graphSubjects);
067        this.graphSubjects = nodeToResource(graphSubjects);
068    }
069
070    /**
071     * This nightmare of Java signature verbosity is a curried transformation.
072     * We want to go from an iterator of JCR {@link Property} to an iterator
073     * of RDF {@link Triple}s. An annoyance: some properties may produce several
074     * triples (multi-valued properties). So we cannot find a simple Property to
075     * Triple mapping. Instead, we wax clever and offer a function from any
076     * specific property to a new function, one that takes multiple values (such
077     * as occur in our multi-valued properties) to multiple triples. In other
078     * words, this is a function the outputs of which are functions specific to
079     * a given JCR property. Each output knows how to take any specific value of
080     * its specific property to a triple representing the fact that its specific
081     * property obtains that specific value on the node to which that property
082     * belongs. All of this is useful because with these operations represented
083     * as functions instead of ordinary methods, which may have side-effects, we
084     * can use efficient machinery to manipulate iterators of the objects in
085     * which we are interested, and that's exactly what we want to do in this
086     * class. See {@link org.fcrepo.kernel.impl.rdf.impl.PropertiesRdfContext#triplesFromProperties} for an
087     * example of the use of this class with {@link ZippingIterator}.
088     *
089     * @see <a href="http://en.wikipedia.org/wiki/Currying">Currying</a>
090     */
091    @Override
092    public Iterator<Triple> apply(final Property p) {
093        return Iterators.transform(new PropertyValueIterator(p), new Function<Value, Triple>() {
094
095            @Override
096            public Triple apply(final Value v) {
097                return propertyvalue2triple(p, v);
098            }
099        });
100    }
101
102    /**
103     * @param p A JCR {@link Property}
104     * @param v The {@link Value} of that Property to use (in the case of
105     *        multi-valued properties)  For single valued properties this
106     *        must be that single value.
107     * @return An RDF {@link Triple} representing that property.
108     */
109    private Triple propertyvalue2triple(final Property p, final Value v) {
110        LOGGER.trace("Rendering triple for Property: {} with Value: {}", p, v);
111        try {
112
113            final Triple triple = create(graphSubjects.convert(p.getParent()).asNode(),
114                    propertyConverter.convert(p).asNode(),
115                    convertObject(p, v));
116
117            LOGGER.trace("Created triple: {} ", triple);
118            return triple;
119        } catch (final RepositoryException e) {
120            throw propagate(e);
121        }
122    }
123
124    private com.hp.hpl.jena.graph.Node convertObject(final Property p, final Value v) throws RepositoryException {
125        final com.hp.hpl.jena.graph.Node object = valueConverter.convert(v).asNode();
126
127        if (object.isLiteral()) {
128            final String propertyName = p.getName();
129            final int i = propertyName.indexOf("@");
130
131            if (i > 0) {
132                final LiteralLabel literal = object.getLiteral();
133                final String datatypeURI = literal.getDatatypeURI();
134
135                if (datatypeURI.isEmpty() || datatypeURI.equals(XSDDatatype.XSDstring.getURI())) {
136
137                    final String lang = propertyName.substring(i + 1);
138                    return createLiteral(literal.getLexicalForm(), lang, literal.getDatatype());
139                }
140            }
141        }
142
143        return object;
144    }
145
146}