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.RdfStream;
027import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
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.fcrepo.kernel.api.RdfCollectors.toModel;
051import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
052import static org.modeshape.jcr.api.JcrConstants.JCR_DATA;
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    private static final Comparator<NodeType> BY_NAME =
065            (final NodeType o1, final NodeType o2) -> o1.getName().compareTo(o2.getName());
066
067    // TODO: this mime type was made up
068    public static final String APPLICATION_RDF_LDPATH = "application/rdf+ldpath";
069    private final InputStream query;
070
071    private static final Logger LOGGER = getLogger(LDPathTransform.class);
072
073    /**
074     * Construct a new Transform from the InputStream
075     * @param query the query
076     */
077    public LDPathTransform(final InputStream query) {
078        this.query = query;
079    }
080
081    /**
082     * Pull a node-type specific transform out of JCR
083     * @param node the node
084     * @param key the key
085     * @return node-type specific transform
086     * @throws RepositoryException if repository exception occurred
087     */
088    public static LDPathTransform getNodeTypeTransform(final Node node,
089        final String key) throws RepositoryException {
090
091        final Node programNode = node.getSession().getNode(CONFIGURATION_FOLDER + key);
092
093        LOGGER.debug("Found program node: {}", programNode.getPath());
094
095        final NodeType primaryNodeType = node.getPrimaryNodeType();
096
097        final Set<NodeType> supertypes = orderedBy(BY_NAME).add(primaryNodeType.getSupertypes()).build();
098        final Set<NodeType> mixinTypes = orderedBy(BY_NAME).add(node.getMixinNodeTypes()).build();
099
100        // start with mixins, primary type, and supertypes of primary type
101        final ImmutableList.Builder<NodeType> nodeTypesB = builder();
102        nodeTypesB.addAll(mixinTypes).add(primaryNodeType).addAll(supertypes);
103
104        // add supertypes of mixins
105        mixinTypes.stream().map(mixin -> orderedBy(BY_NAME).add(mixin.getDeclaredSupertypes()).build())
106            .forEach(nodeTypesB::addAll);
107
108        final List<NodeType> nodeTypes = nodeTypesB.build();
109
110        LOGGER.debug("Discovered node types: {}", nodeTypes);
111        for (final NodeType nodeType : nodeTypes) {
112            if (programNode.hasNode(nodeType.toString())) {
113                return new LDPathTransform(programNode.getNode(nodeType.toString())
114                                               .getNode(JCR_CONTENT)
115                                               .getProperty(JCR_DATA)
116                                               .getBinary().getStream());
117            }
118        }
119
120        throw new WebApplicationException(new Exception(
121                "Couldn't find transformation for " + node.getPath()
122                        + " and transformation key " + key), SC_BAD_REQUEST);
123    }
124
125    @Override
126    public List<Map<String, Collection<Object>>> apply(final RdfStream stream) {
127        final LDPath<RDFNode> ldpathForResource =
128            getLdpathResource(stream);
129
130        final Resource context = createResource(stream.topic().getURI());
131
132        try {
133            return ImmutableList.of(unsafeCast(
134                ldpathForResource.programQuery(context, new InputStreamReader(query))));
135        } catch (final LDPathParseException e) {
136            throw new RepositoryRuntimeException(e);
137        }
138    }
139
140    @SuppressWarnings("unchecked")
141    private static <F, T> T unsafeCast(final F from) {
142        return (T) from;
143    }
144
145    @Override
146    public boolean equals(final Object other) {
147        return other instanceof LDPathTransform && ((LDPathTransform) other).query.equals(query);
148    }
149
150    @Override
151    public int hashCode() {
152        return Objects.hashCode(query);
153    }
154
155    /**
156     * Get the LDPath resource for an object
157     * @param rdfStream
158     * @return the LDPath resource for the given object
159     */
160    private static LDPath<RDFNode> getLdpathResource(final RdfStream rdfStream) {
161
162        return new LDPath<>(new GenericJenaBackend(rdfStream.collect(toModel())));
163
164    }
165}