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.base.Function;
019import com.google.common.collect.ImmutableList;
020import com.hp.hpl.jena.rdf.model.RDFNode;
021import com.hp.hpl.jena.rdf.model.Resource;
022
023import org.apache.marmotta.ldpath.LDPath;
024import org.apache.marmotta.ldpath.backend.jena.GenericJenaBackend;
025import org.apache.marmotta.ldpath.exception.LDPathParseException;
026import org.fcrepo.kernel.exception.RepositoryRuntimeException;
027import org.fcrepo.kernel.utils.iterators.RdfStream;
028import org.fcrepo.transform.Transformation;
029import org.slf4j.Logger;
030
031import javax.jcr.Node;
032import javax.jcr.RepositoryException;
033import javax.jcr.nodetype.NodeType;
034import javax.ws.rs.WebApplicationException;
035
036import java.io.InputStream;
037import java.io.InputStreamReader;
038import java.util.Collection;
039import java.util.Comparator;
040import java.util.List;
041import java.util.Map;
042import java.util.Objects;
043import java.util.Set;
044
045import static com.google.common.collect.Collections2.transform;
046import static com.google.common.collect.ImmutableSortedSet.orderedBy;
047import static com.google.common.collect.Maps.transformValues;
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
064    // TODO: this mime type was made up
065    public static final String APPLICATION_RDF_LDPATH = "application/rdf+ldpath";
066    private final InputStream query;
067
068    private static final Logger LOGGER = getLogger(LDPathTransform.class);
069
070    private static final Comparator<NodeType> nodeTypeComp = new Comparator<NodeType>() {
071
072        @Override
073        public int compare(final NodeType o1, final NodeType o2) {
074            return o1.getName().compareTo(o2.getName());
075
076        }
077    };
078
079    /**
080     * Construct a new Transform from the InputStream
081     * @param query the query
082     */
083    public LDPathTransform(final InputStream query) {
084        this.query = query;
085    }
086
087    /**
088     * Pull a node-type specific transform out of JCR
089     * @param node the node
090     * @param key the key
091     * @return node-type specific transform
092     * @throws RepositoryException if repository exception occurred
093     */
094    public static LDPathTransform getNodeTypeTransform(final Node node,
095        final String key) throws RepositoryException {
096
097        final Node programNode = node.getSession().getNode(CONFIGURATION_FOLDER + key);
098
099        LOGGER.debug("Found program node: {}", programNode.getPath());
100
101        final NodeType primaryNodeType = node.getPrimaryNodeType();
102
103        final Set<NodeType> supertypes =
104            orderedBy(nodeTypeComp).add(primaryNodeType.getSupertypes())
105                    .build();
106        final Set<NodeType> mixinTypes =
107            orderedBy(nodeTypeComp).add(node.getMixinNodeTypes()).build();
108
109        // start with mixins, primary type, and supertypes of primary type
110        final ImmutableList.Builder<NodeType> nodeTypesB =
111            new ImmutableList.Builder<NodeType>().addAll(mixinTypes).add(
112                    primaryNodeType).addAll(supertypes);
113
114        // add supertypes of mixins
115        for (final NodeType mixin : mixinTypes) {
116            nodeTypesB.addAll(orderedBy(nodeTypeComp).add(
117                    mixin.getDeclaredSupertypes()).build());
118        }
119
120        final List<NodeType> nodeTypes = nodeTypesB.build();
121
122        LOGGER.debug("Discovered node types: {}", nodeTypes);
123
124        for (final NodeType nodeType : nodeTypes) {
125            if (programNode.hasNode(nodeType.toString())) {
126                return new LDPathTransform(programNode.getNode(nodeType.toString())
127                                               .getNode(JCR_CONTENT)
128                                               .getProperty(JCR_DATA)
129                                               .getBinary().getStream());
130            }
131        }
132
133        throw new WebApplicationException(new Exception(
134                "Couldn't find transformation for " + node.getPath()
135                        + " and transformation key " + key), SC_BAD_REQUEST);
136    }
137
138    @Override
139    public List<Map<String, Collection<Object>>> apply(final RdfStream stream) {
140        try {
141            final LDPath<RDFNode> ldpathForResource =
142                getLdpathResource(stream);
143
144            final Resource context = createResource(stream.topic().getURI());
145
146            final Map<String, Collection<?>> wildcardCollection =
147                ldpathForResource.programQuery(context, new InputStreamReader(
148                        query));
149
150            return ImmutableList.of(transformLdpathOutputToSomethingSerializable(wildcardCollection));
151        } catch (final LDPathParseException e) {
152            throw new RepositoryRuntimeException(e);
153        }
154    }
155
156    @Override
157    public InputStream getQuery() {
158        return query;
159    }
160
161    @Override
162    public boolean equals(final Object other) {
163        return other instanceof LDPathTransform &&
164                   query.equals(((LDPathTransform)other).getQuery());
165    }
166
167    @Override
168    public int hashCode() {
169        return Objects.hashCode(getQuery());
170    }
171
172    /**
173     * Get the LDPath resource for an object
174     * @param rdfStream
175     * @return the LDPath resource for the given object
176     */
177    private static LDPath<RDFNode> getLdpathResource(final RdfStream rdfStream) {
178
179        return new LDPath<>(new GenericJenaBackend(rdfStream.asModel()));
180
181    }
182
183    /**
184     * In order for the JAX-RS serialization magic to work, we have to turn the map into
185     * a non-wildcard type.
186     * @param collectionMap
187     * @return map of the LDPath
188     */
189    private static Map<String, Collection<Object>> transformLdpathOutputToSomethingSerializable(
190        final Map<String, Collection<?>> collectionMap) {
191
192        return transformValues(collectionMap,
193                WILDCARD_COLLECTION_TO_OBJECT_COLLECTION);
194    }
195
196    private static final Function<Collection<?>, Collection<Object>> WILDCARD_COLLECTION_TO_OBJECT_COLLECTION =
197        new Function<Collection<?>, Collection<Object>>() {
198
199            @Override
200            public Collection<Object> apply(final Collection<?> input) {
201                return transform(input, ANYTHING_TO_OBJECT_FUNCTION);
202            }
203        };
204
205    private static final Function<Object, Object> ANYTHING_TO_OBJECT_FUNCTION =
206        new Function<Object, Object>() {
207
208            @Override
209            public Object apply(final Object input) {
210                return input;
211            }
212        };
213
214    @Override
215    public LDPathTransform newTransform(final InputStream query) {
216        return new LDPathTransform(query);
217    }
218}