001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.kernel.modeshape.rdf.impl.mappings;
019
020import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring;
021import static org.apache.jena.graph.NodeFactory.createLiteral;
022import static org.apache.jena.graph.Triple.create;
023import static org.fcrepo.kernel.modeshape.identifiers.NodeResourceConverter.nodeToResource;
024import static org.fcrepo.kernel.modeshape.utils.StreamUtils.iteratorToStream;
025import static org.slf4j.LoggerFactory.getLogger;
026
027import java.util.function.Function;
028import java.util.stream.Stream;
029
030import javax.jcr.Node;
031import javax.jcr.Property;
032import javax.jcr.RepositoryException;
033import javax.jcr.Session;
034import javax.jcr.Value;
035
036import com.google.common.base.Converter;
037import org.apache.jena.graph.impl.LiteralLabel;
038import org.apache.jena.rdf.model.Resource;
039
040import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
041import org.fcrepo.kernel.api.models.FedoraResource;
042import org.fcrepo.kernel.modeshape.rdf.converters.PropertyConverter;
043import org.fcrepo.kernel.modeshape.rdf.converters.ValueConverter;
044import org.apache.jena.datatypes.RDFDatatype;
045import org.apache.jena.graph.Triple;
046import org.slf4j.Logger;
047
048/**
049 * Utility for moving from JCR properties to RDF triples.
050 *
051 * @author ajs6f
052 * @since Oct 10, 2013
053 */
054public class PropertyToTriple implements Function<Property, Stream<Triple>> {
055
056    private static final Logger LOGGER = getLogger(PropertyToTriple.class);
057
058    private static final PropertyConverter propertyConverter = new PropertyConverter();
059    private final ValueConverter valueConverter;
060    private final Converter<Node, Resource> translator;
061
062    /**
063     * Default constructor. We require a {@link Converter} in order to construct the RDF subjects of our triples.
064     *
065     * @param converter a converter between RDF and the Fedora model
066     * @param session the JCR session
067     */
068    public PropertyToTriple(final Session session, final Converter<Resource, FedoraResource> converter) {
069        this.valueConverter = new ValueConverter(session, converter);
070        this.translator = nodeToResource(converter);
071    }
072
073    @Override
074    public Stream<Triple> apply(final Property p) {
075        try {
076            final org.apache.jena.graph.Node subject = translator.convert(p.getParent()).asNode();
077            final org.apache.jena.graph.Node propPredicate = propertyConverter.convert(p).asNode();
078            final String propertyName = p.getName();
079
080            return iteratorToStream(new PropertyValueIterator(p)).filter(this::valueCanBeConverted).map(v -> {
081                final org.apache.jena.graph.Node object = valueConverter.convert(v).asNode();
082                if (object.isLiteral()) {
083                    // unpack the name of the property for information about what kind of literal
084                    final int i = propertyName.indexOf('@');
085                    if (i > 0) {
086                        final LiteralLabel literal = object.getLiteral();
087                        final RDFDatatype datatype = literal.getDatatype();
088                        final String datatypeURI = datatype.getURI();
089                        if (datatypeURI.isEmpty() || datatype.equals(XSDstring)) {
090                            // this is an RDF string literal and could involve an RDF lang tag
091                            final String lang = propertyName.substring(i + 1);
092                            final String lex = literal.getLexicalForm();
093                            return create(subject, propPredicate, createLiteral(lex, lang, datatype));
094                        }
095                    }
096                }
097                return create(subject, propPredicate, object);
098            });
099        } catch (final RepositoryException e) {
100            throw new RepositoryRuntimeException(e);
101        }
102    }
103
104    /**
105     * This method tests if a given value can be converted.
106     * The scenario when this may not be true is for (weak)reference properties that target an non-existent resource.
107     * This scenario generally should not be possible, but the following bug introduced the possibility:
108     *   https://jira.duraspace.org/browse/FCREPO-2323
109     *
110     * @param value to be tested for whether it can be converted to an RDFNode or not
111     * @return true if value can be converted
112     */
113    private boolean valueCanBeConverted(final Value value) {
114        try {
115            valueConverter.convert(value);
116            return true;
117        } catch (final RepositoryRuntimeException e) {
118            LOGGER.warn("Reference to non-existent resource encounterd: {}", value);
119            return false;
120        }
121    };
122}