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