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