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.reindexing;
019
020import static org.fcrepo.camel.FcrepoHeaders.FCREPO_BASE_URL;
021import static org.slf4j.LoggerFactory.getLogger;
022
023import javax.xml.transform.stream.StreamSource;
024
025import org.apache.camel.Exchange;
026import org.apache.camel.LoggingLevel;
027import org.apache.camel.PropertyInject;
028import org.apache.camel.builder.RouteBuilder;
029import org.apache.camel.builder.xml.Namespaces;
030import org.fcrepo.client.HttpMethods;
031import org.fcrepo.camel.RdfNamespaces;
032import org.slf4j.Logger;
033
034/**
035 * A content router for handling JMS events.
036 *
037 * @author Aaron Coburn
038 */
039public class ReindexingRouter extends RouteBuilder {
040
041    private static final Logger LOGGER = getLogger(ReindexingRouter.class);
042    private static final int BAD_REQUEST = 400;
043
044    @PropertyInject(value = "rest.port", defaultValue = "9080")
045    private String port;
046
047    @PropertyInject(value = "rest.host", defaultValue = "localhost")
048    private String host;
049
050    /**
051     * Configure the message route workflow.
052     */
053    public void configure() throws Exception {
054
055        final String hostname = host.startsWith("http") ? host : "http://" + host;
056
057        final Namespaces ns = new Namespaces("rdf", RdfNamespaces.RDF);
058        ns.add("ldp", RdfNamespaces.LDP);
059
060        /**
061         * A generic error handler (specific to this RouteBuilder)
062         */
063        onException(Exception.class)
064            .maximumRedeliveries("{{error.maxRedeliveries}}")
065            .log("Index Routing Error: ${routeId}");
066
067        /**
068         * Expose a RESTful endpoint for re-indexing
069         */
070        from("jetty:" + hostname + ":" + port + "{{rest.prefix}}?matchOnUriPrefix=true&httpMethodRestrict=GET,POST")
071            .routeId("FcrepoReindexingRest")
072            .routeDescription("Expose the reindexing endpoint over HTTP")
073            .choice()
074                .when(header(Exchange.HTTP_METHOD).isEqualTo("GET"))
075                    .to("direct:usage")
076                .otherwise()
077                    .to("direct:reindex");
078
079        from("direct:usage")
080            .routeId("FcrepoReindexingUsage")
081            .setHeader(ReindexingHeaders.REST_PREFIX).simple("{{rest.prefix}}")
082            .setHeader(ReindexingHeaders.REST_PORT).simple(port)
083            .setHeader(FCREPO_BASE_URL).simple("{{fcrepo.baseUrl}}")
084            .process(new UsageProcessor());
085
086        /**
087         * A Re-indexing endpoint, setting where in the fcrepo hierarchy
088         * a re-indexing operation should begin.
089         */
090        from("direct:reindex")
091            .routeId("FcrepoReindexingReindex")
092            .setHeader(ReindexingHeaders.REST_PREFIX).simple("{{rest.prefix}}")
093            .setHeader(FCREPO_BASE_URL).simple("{{fcrepo.baseUrl}}")
094            .process(new RestProcessor())
095            .choice()
096                .when(header(Exchange.HTTP_RESPONSE_CODE).isGreaterThanOrEqualTo(BAD_REQUEST))
097                    .endChoice()
098                .when(header(ReindexingHeaders.RECIPIENTS).isEqualTo(""))
099                    .transform().simple("No endpoints configured for indexing")
100                    .endChoice()
101                .otherwise()
102                    .log(LoggingLevel.INFO, LOGGER, "Initial indexing path: ${headers[CamelFcrepoIdentifier]}")
103                    .inOnly("{{reindexing.stream}}?disableTimeToLive=true")
104                    .setHeader(Exchange.CONTENT_TYPE).constant("text/plain")
105                    .transform().simple("Indexing started at ${headers[CamelFcrepoIdentifier]}");
106
107        /**
108         *  A route that traverses through a fedora heirarchy
109         *  indexing nodes, as appropriate.
110         */
111        from("{{reindexing.stream}}?asyncConsumer=true")
112            .routeId("FcrepoReindexingTraverse")
113            .inOnly("direct:recipients")
114            .removeHeaders("CamelHttp*")
115            .setHeader(Exchange.HTTP_METHOD).constant(HttpMethods.GET)
116            .to("fcrepo:{{fcrepo.baseUrl}}?preferInclude=PreferContainment" +
117                    "&preferOmit=ServerManaged&accept=application/rdf+xml")
118            .convertBodyTo(StreamSource.class)
119            .split().xtokenize("/rdf:RDF/rdf:Description/ldp:contains", 'i', ns).streaming()
120                .transform().xpath("/ldp:contains/@rdf:resource", String.class, ns)
121                .process(new PathProcessor())
122                .inOnly("{{reindexing.stream}}?disableTimeToLive=true");
123
124        /**
125         *  Send the message to all of the pre-determined endpoints
126         */
127        from("direct:recipients")
128            .routeId("FcrepoReindexingRecipients")
129            .recipientList(header(ReindexingHeaders.RECIPIENTS))
130            .ignoreInvalidEndpoints();
131    }
132}