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.transform.transformations;
019
020import com.google.common.collect.ImmutableList;
021import com.hp.hpl.jena.rdf.model.RDFNode;
022import com.hp.hpl.jena.rdf.model.Resource;
023
024import org.apache.marmotta.ldpath.LDPath;
025import org.apache.marmotta.ldpath.backend.jena.GenericJenaBackend;
026import org.apache.marmotta.ldpath.exception.LDPathParseException;
027
028import org.fcrepo.kernel.api.RdfStream;
029import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
030import org.fcrepo.kernel.api.models.FedoraBinary;
031import org.fcrepo.kernel.api.models.FedoraResource;
032import org.fcrepo.kernel.api.services.NodeService;
033import org.fcrepo.transform.TransformNotFoundException;
034import org.fcrepo.transform.Transformation;
035
036import org.slf4j.Logger;
037
038import javax.jcr.NamespaceRegistry;
039import javax.jcr.RepositoryException;
040import javax.jcr.Session;
041import java.io.InputStream;
042import java.io.InputStreamReader;
043import java.net.URI;
044import java.util.Collection;
045import java.util.List;
046import java.util.Map;
047import java.util.Objects;
048import java.util.function.Function;
049import java.util.stream.Collectors;
050
051import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
052import static org.fcrepo.kernel.api.RdfCollectors.toModel;
053import static org.slf4j.LoggerFactory.getLogger;
054
055/**
056 * Utilities for working with LDPath
057 *
058 * @author cbeer
059 */
060public class LDPathTransform implements Transformation<List<Map<String, Collection<Object>>>>  {
061
062    public static final String CONFIGURATION_FOLDER = "/fedora:system/fedora:transform/fedora:ldpath/";
063
064    public static final String DEFAULT_TRANSFORM_RESOURCE = "fedora:Resource";
065
066    // TODO: this mime type was made up
067    public static final String APPLICATION_RDF_LDPATH = "application/rdf+ldpath";
068    private final InputStream query;
069
070    private static final Logger LOGGER = getLogger(LDPathTransform.class);
071
072    /**
073     * Construct a new Transform from the InputStream
074     * @param query the query
075     */
076    public LDPathTransform(final InputStream query) {
077        this.query = query;
078    }
079
080    /**
081     * Pull a resource-type specific transform for the specified key
082     * @param resource the resource
083     * @param session the session
084     * @param nodeService a nodeService
085     * @param key the key
086     * @return resource-type specific transform
087     * @throws RepositoryException if repository exception occurred
088     */
089    public static LDPathTransform getResourceTransform(final FedoraResource resource, final Session session,
090            final NodeService nodeService, final String key) throws RepositoryException {
091
092        final FedoraResource transformResource = nodeService.find(session, CONFIGURATION_FOLDER + key);
093
094        LOGGER.debug("Found transform resource: {}", transformResource.getPath());
095
096        final List<URI> rdfTypes = resource.getTypes();
097
098        LOGGER.debug("Discovered rdf types: {}", rdfTypes);
099
100        final NamespaceRegistry nsRegistry = session.getWorkspace().getNamespaceRegistry();
101
102        // convert rdf:type with URI namespace to prefixed namespace
103        final Function<URI, String> namespaceUriToPrefix = x -> {
104            final String uriString = x.toString();
105            try {
106                for (final String namespace : nsRegistry.getURIs()) {
107                    // Ignoring zero-length namespaces return the appropriate prefix
108                    if (namespace.length() > 0 && uriString.startsWith(namespace)) {
109                        return uriString.replace(namespace, nsRegistry.getPrefix(namespace) + ":");
110                    }
111                }
112                return uriString;
113            } catch (final RepositoryException e) {
114                return uriString;
115            }
116        };
117
118        final List<String> rdfStringTypes = rdfTypes.stream().map(namespaceUriToPrefix)
119                .map(stringType -> transformResource.getPath() + "/" + stringType)
120                .collect(Collectors.toList());
121
122        final FedoraBinary transform = (FedoraBinary) transformResource.getChildren()
123                .filter(child -> rdfStringTypes.contains(child.getPath()))
124                .findFirst()
125                .orElseThrow(() -> new TransformNotFoundException(
126                    String.format("Couldn't find transformation for {} and transformation key {}",
127                    resource.getPath(), key)));
128        return new LDPathTransform(transform.getContent());
129    }
130
131    @Override
132    public List<Map<String, Collection<Object>>> apply(final RdfStream stream) {
133        final LDPath<RDFNode> ldpathForResource =
134            getLdpathResource(stream);
135
136        final Resource context = createResource(stream.topic().getURI());
137
138        try {
139            return ImmutableList.of(unsafeCast(
140                ldpathForResource.programQuery(context, new InputStreamReader(query))));
141        } catch (final LDPathParseException e) {
142            throw new RepositoryRuntimeException(e);
143        }
144    }
145
146    @SuppressWarnings("unchecked")
147    private static <F, T> T unsafeCast(final F from) {
148        return (T) from;
149    }
150
151    @Override
152    public boolean equals(final Object other) {
153        return other instanceof LDPathTransform && ((LDPathTransform) other).query.equals(query);
154    }
155
156    @Override
157    public int hashCode() {
158        return Objects.hashCode(query);
159    }
160
161    /**
162     * Get the LDPath resource for an object
163     * @param rdfStream
164     * @return the LDPath resource for the given object
165     */
166    private static LDPath<RDFNode> getLdpathResource(final RdfStream rdfStream) {
167
168        return new LDPath<>(new GenericJenaBackend(rdfStream.collect(toModel())));
169
170    }
171}