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.converters;
019
020import com.google.common.base.Converter;
021import org.apache.jena.rdf.model.Property;
022import org.apache.jena.rdf.model.Resource;
023
024import org.fcrepo.kernel.api.exception.FedoraInvalidNamespaceException;
025import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
026import org.modeshape.jcr.api.NamespaceRegistry;
027import org.modeshape.jcr.api.Namespaced;
028import org.slf4j.Logger;
029
030import javax.jcr.Node;
031import javax.jcr.RepositoryException;
032
033import java.util.Map;
034import static org.apache.jena.rdf.model.ResourceFactory.createProperty;
035import static org.fcrepo.kernel.modeshape.rdf.JcrRdfTools.getJcrNamespaceForRDFNamespace;
036import static org.fcrepo.kernel.modeshape.rdf.JcrRdfTools.getRDFNamespaceForJcrNamespace;
037import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.getReferencePropertyOriginalName;
038import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isInternalReferenceProperty;
039import static org.fcrepo.kernel.modeshape.utils.NamespaceTools.getNamespaceRegistry;
040import static org.slf4j.LoggerFactory.getLogger;
041
042
043/**
044 * Convert between RDF properties and JCR properties
045 * @author cabeer
046 * @since 10/8/14
047 */
048public class PropertyConverter extends Converter<javax.jcr.Property, Property> {
049    private static final Logger LOGGER = getLogger(PropertyConverter.class);
050
051    @Override
052    protected Property doForward(final javax.jcr.Property property) {
053        LOGGER.trace("Creating predicate for property: {}",
054                property);
055        try {
056            if (property instanceof Namespaced) {
057                final Namespaced nsProperty = (Namespaced) property;
058                final String uri = nsProperty.getNamespaceURI();
059                final String localName = nsProperty.getLocalName();
060                final String rdfLocalName;
061
062                if (isInternalReferenceProperty.test(property)) {
063                    rdfLocalName = getReferencePropertyOriginalName(localName);
064                } else {
065                    rdfLocalName = localName;
066                }
067                return createProperty(
068                        getRDFNamespaceForJcrNamespace(uri),
069                        rdfLocalName);
070            }
071            return createProperty(property.getName());
072        } catch (final RepositoryException e) {
073            throw new RepositoryRuntimeException(e);
074        }
075
076    }
077
078    @Override
079    protected javax.jcr.Property doBackward(final Property property) {
080        throw new UnsupportedOperationException();
081    }
082
083    /**
084     * Given an RDF predicate value (namespace URI + local name), figure out
085     * what JCR property to use
086     *
087     * @param node the JCR node we want a property for
088     * @param predicate the predicate to map to a property name
089     * @param namespaceMapping prefix to uri namespace mapping
090     * @return the JCR property name
091     * @throws RepositoryException if repository exception occurred
092     */
093    public static String getPropertyNameFromPredicate(final Node node,
094                                                      final Resource predicate,
095                                                      final Map<String, String> namespaceMapping)
096            throws RepositoryException {
097
098        final NamespaceRegistry namespaceRegistry = (NamespaceRegistry)getNamespaceRegistry(node.getSession());
099
100        return getPropertyNameFromPredicate(namespaceRegistry,
101                predicate, namespaceMapping);
102    }
103
104    /**
105     * Get the JCR property name for an RDF predicate
106     *
107     * @param namespaceRegistry the namespace registry
108     * @param predicate the predicate to map to a property name
109     * @param namespaceMapping the namespace mapping
110     * @return JCR property name for an RDF predicate
111     * @throws RepositoryException if repository exception occurred
112     */
113    private static String getPropertyNameFromPredicate(final NamespaceRegistry namespaceRegistry,
114                                                       final Resource predicate,
115                                                       final Map<String, String> namespaceMapping)
116            throws RepositoryException {
117
118        // reject if update request contains any fcr namespaces
119        if (namespaceMapping != null && namespaceMapping.containsKey("fcr")) {
120            throw new FedoraInvalidNamespaceException("Invalid fcr namespace properties " + predicate + ".");
121        }
122
123        final String rdfNamespace = predicate.getNameSpace();
124
125        // log warning if the user-supplied namespace doesn't match value from predicate.getNameSpace(),
126        // e.g., if the Jena method returns "http://" for "http://myurl.org" (no terminating character).
127        if (namespaceMapping != null && namespaceMapping.size() > 0 && !namespaceMapping.containsValue(rdfNamespace)) {
128            LOGGER.warn("The namespace of predicate: {} was possibly misinterpreted as: {}."
129                    , predicate, rdfNamespace);
130        }
131
132        final String rdfLocalname = predicate.getLocalName();
133
134        final String prefix;
135
136        assert (namespaceRegistry != null);
137
138        final String namespace = getJcrNamespaceForRDFNamespace(rdfNamespace);
139
140        if (namespaceRegistry.isRegisteredUri(namespace)) {
141            LOGGER.debug("Discovered namespace: {} in namespace registry.",namespace);
142            prefix = namespaceRegistry.getPrefix(namespace);
143        } else {
144            LOGGER.debug("Didn't discover namespace: {} in namespace registry.",namespace);
145            if (namespaceMapping != null && namespaceMapping.containsValue(namespace)) {
146                LOGGER.debug("Discovered namespace: {} in namespace map: {}.", namespace,
147                    namespaceMapping);
148                prefix = namespaceMapping.entrySet().stream()
149                    .filter(t -> t.getValue().equals(namespace))
150                    .map(Map.Entry::getKey).findFirst().orElse(null);
151                namespaceRegistry.registerNamespace(prefix, namespace);
152            } else {
153                prefix = namespaceRegistry.registerNamespace(namespace);
154                LOGGER.debug("Registered prefix: {} for namespace: {}.", prefix, namespace);
155            }
156        }
157
158        final String propertyName = prefix + ":" + rdfLocalname;
159
160        LOGGER.debug("Took RDF predicate {} and translated it to JCR property {}", namespace, propertyName);
161
162        return propertyName;
163
164    }
165
166}