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.camel.indexing.triplestore;
019
020import static java.util.stream.Collectors.toList;
021import static org.apache.camel.builder.PredicateBuilder.in;
022import static org.apache.camel.builder.PredicateBuilder.not;
023import static org.apache.camel.builder.PredicateBuilder.or;
024import static org.fcrepo.camel.FcrepoHeaders.FCREPO_NAMED_GRAPH;
025import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_TYPE;
026import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI;
027import static org.fcrepo.camel.processor.ProcessorUtils.tokenizePropertyPlaceholder;
028import static org.slf4j.LoggerFactory.getLogger;
029
030import org.apache.camel.LoggingLevel;
031import org.apache.camel.builder.RouteBuilder;
032import org.apache.camel.builder.xml.Namespaces;
033import org.apache.camel.builder.xml.XPathBuilder;
034import org.fcrepo.camel.processor.EventProcessor;
035import org.fcrepo.camel.processor.SparqlDeleteProcessor;
036import org.fcrepo.camel.processor.SparqlUpdateProcessor;
037import org.slf4j.Logger;
038
039/**
040 * A content router for handling Fedora events.
041 *
042 * @author Aaron Coburn
043 */
044public class TriplestoreRouter extends RouteBuilder {
045
046    private static final Logger LOGGER = getLogger(TriplestoreRouter.class);
047
048    private static final String RESOURCE_DELETION = "http://fedora.info/definitions/v4/event#ResourceDeletion";
049    private static final String DELETE = "https://www.w3.org/ns/activitystreams#Delete";
050
051    /**
052     * Configure the message route workflow.
053     */
054    public void configure() throws Exception {
055
056        final Namespaces ns = new Namespaces("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
057        ns.add("indexing", "http://fedora.info/definitions/v4/indexing#");
058
059        final XPathBuilder indexable = new XPathBuilder(
060                String.format("/rdf:RDF/rdf:Description/rdf:type[@rdf:resource='%s']",
061                    "http://fedora.info/definitions/v4/indexing#Indexable"));
062        indexable.namespaces(ns);
063
064        /**
065         * A generic error handler (specific to this RouteBuilder)
066         */
067        onException(Exception.class)
068            .maximumRedeliveries("{{error.maxRedeliveries}}")
069            .log("Index Routing Error: ${routeId}");
070
071        /**
072         * route a message to the proper queue, based on whether
073         * it is a DELETE or UPDATE operation.
074         */
075        from("{{input.stream}}")
076            .routeId("FcrepoTriplestoreRouter")
077            .process(new EventProcessor())
078            .choice()
079                .when(or(header(FCREPO_EVENT_TYPE).contains(RESOURCE_DELETION),
080                            header(FCREPO_EVENT_TYPE).contains(DELETE)))
081                    .to("direct:delete.triplestore")
082                .otherwise()
083                    .to("direct:index.triplestore");
084
085        /**
086         * Handle re-index events
087         */
088        from("{{triplestore.reindex.stream}}")
089            .routeId("FcrepoTriplestoreReindex")
090            .to("direct:index.triplestore");
091
092        /**
093         * Based on an item's metadata, determine if it is indexable.
094         */
095        from("direct:index.triplestore")
096            .routeId("FcrepoTriplestoreIndexer")
097            .filter(not(in(tokenizePropertyPlaceholder(getContext(), "{{filter.containers}}", ",").stream()
098                        .map(uri -> or(
099                            header(FCREPO_URI).startsWith(constant(uri + "/")),
100                            header(FCREPO_URI).isEqualTo(constant(uri))))
101                        .collect(toList()))))
102            .removeHeaders("CamelHttp*")
103            .choice()
104                .when(simple("{{indexing.predicate}} != 'true'"))
105                    .to("direct:update.triplestore")
106                .otherwise()
107                    .to("fcrepo:{{fcrepo.baseUrl}}?preferInclude=PreferMinimalContainer&accept=application/rdf+xml")
108                    .choice()
109                        .when(indexable)
110                            .to("direct:update.triplestore")
111                        .otherwise()
112                            .to("direct:delete.triplestore");
113
114        /**
115         * Remove an item from the triplestore index.
116         */
117        from("direct:delete.triplestore")
118            .routeId("FcrepoTriplestoreDeleter")
119            .process(new SparqlDeleteProcessor())
120            .log(LoggingLevel.INFO, LOGGER,
121                    "Deleting Triplestore Object ${headers[CamelFcrepoUri]}")
122            .to("{{triplestore.baseUrl}}?useSystemProperties=true");
123
124        /**
125         * Perform the sparql update.
126         */
127        from("direct:update.triplestore")
128            .routeId("FcrepoTriplestoreUpdater")
129            .setHeader(FCREPO_NAMED_GRAPH)
130                .simple("{{triplestore.namedGraph}}")
131            .to("fcrepo:{{fcrepo.baseUrl}}?accept=application/n-triples" +
132                    "&preferOmit={{prefer.omit}}&preferInclude={{prefer.include}}")
133            .process(new SparqlUpdateProcessor())
134            .log(LoggingLevel.INFO, LOGGER,
135                    "Indexing Triplestore Object ${headers[CamelFcrepoUri]}")
136            .to("{{triplestore.baseUrl}}?useSystemProperties=true");
137    }
138}