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.transform.transformations;
017
018import com.google.common.collect.ImmutableList;
019import com.hp.hpl.jena.rdf.model.RDFNode;
020import com.hp.hpl.jena.rdf.model.Resource;
021
022import org.apache.marmotta.ldpath.LDPath;
023import org.apache.marmotta.ldpath.backend.jena.GenericJenaBackend;
024import org.apache.marmotta.ldpath.exception.LDPathParseException;
025
026import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
027import org.fcrepo.kernel.api.utils.iterators.RdfStream;
028import org.fcrepo.transform.Transformation;
029
030import org.slf4j.Logger;
031
032import javax.jcr.Node;
033import javax.jcr.RepositoryException;
034import javax.jcr.nodetype.NodeType;
035import javax.ws.rs.WebApplicationException;
036
037import java.io.InputStream;
038import java.io.InputStreamReader;
039import java.util.Collection;
040import java.util.Comparator;
041import java.util.List;
042import java.util.Map;
043import java.util.Objects;
044import java.util.Set;
045
046import static com.google.common.collect.ImmutableList.builder;
047import static com.google.common.collect.ImmutableSortedSet.orderedBy;
048import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
049import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
050import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
051import static org.modeshape.jcr.api.JcrConstants.JCR_DATA;
052import static org.slf4j.LoggerFactory.getLogger;
053
054/**
055 * Utilities for working with LDPath
056 *
057 * @author cbeer
058 */
059public class LDPathTransform implements Transformation<List<Map<String, Collection<Object>>>>  {
060
061    public static final String CONFIGURATION_FOLDER = "/fedora:system/fedora:transform/fedora:ldpath/";
062
063    private static final Comparator<NodeType> BY_NAME =
064            (final NodeType o1, final NodeType o2) -> o1.getName().compareTo(o2.getName());
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 node-type specific transform out of JCR
082     * @param node the node
083     * @param key the key
084     * @return node-type specific transform
085     * @throws RepositoryException if repository exception occurred
086     */
087    public static LDPathTransform getNodeTypeTransform(final Node node,
088        final String key) throws RepositoryException {
089
090        final Node programNode = node.getSession().getNode(CONFIGURATION_FOLDER + key);
091
092        LOGGER.debug("Found program node: {}", programNode.getPath());
093
094        final NodeType primaryNodeType = node.getPrimaryNodeType();
095
096        final Set<NodeType> supertypes = orderedBy(BY_NAME).add(primaryNodeType.getSupertypes()).build();
097        final Set<NodeType> mixinTypes = orderedBy(BY_NAME).add(node.getMixinNodeTypes()).build();
098
099        // start with mixins, primary type, and supertypes of primary type
100        final ImmutableList.Builder<NodeType> nodeTypesB = builder();
101        nodeTypesB.addAll(mixinTypes).add(primaryNodeType).addAll(supertypes);
102
103        // add supertypes of mixins
104        mixinTypes.stream().map(mixin -> orderedBy(BY_NAME).add(mixin.getDeclaredSupertypes()).build())
105            .forEach(nodeTypesB::addAll);
106
107        final List<NodeType> nodeTypes = nodeTypesB.build();
108
109        LOGGER.debug("Discovered node types: {}", nodeTypes);
110        for (final NodeType nodeType : nodeTypes) {
111            if (programNode.hasNode(nodeType.toString())) {
112                return new LDPathTransform(programNode.getNode(nodeType.toString())
113                                               .getNode(JCR_CONTENT)
114                                               .getProperty(JCR_DATA)
115                                               .getBinary().getStream());
116            }
117        }
118
119        throw new WebApplicationException(new Exception(
120                "Couldn't find transformation for " + node.getPath()
121                        + " and transformation key " + key), SC_BAD_REQUEST);
122    }
123
124    @Override
125    public List<Map<String, Collection<Object>>> apply(final RdfStream stream) {
126        final LDPath<RDFNode> ldpathForResource =
127            getLdpathResource(stream);
128
129        final Resource context = createResource(stream.topic().getURI());
130
131        try {
132            return ImmutableList.of(unsafeCast(
133                ldpathForResource.programQuery(context, new InputStreamReader(query))));
134        } catch (final LDPathParseException e) {
135            throw new RepositoryRuntimeException(e);
136        }
137    }
138
139    @SuppressWarnings("unchecked")
140    private static <F, T> T unsafeCast(final F from) {
141        return (T) from;
142    }
143
144    @Override
145    public boolean equals(final Object other) {
146        return other instanceof LDPathTransform && ((LDPathTransform) other).query.equals(query);
147    }
148
149    @Override
150    public int hashCode() {
151        return Objects.hashCode(query);
152    }
153
154    /**
155     * Get the LDPath resource for an object
156     * @param rdfStream
157     * @return the LDPath resource for the given object
158     */
159    private static LDPath<RDFNode> getLdpathResource(final RdfStream rdfStream) {
160
161        return new LDPath<>(new GenericJenaBackend(rdfStream.asModel()));
162
163    }
164}