/* * Licensed to DuraSpace under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. * * DuraSpace licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fcrepo.camel.indexing.solr; import static java.util.stream.Collectors.toList; import static org.apache.camel.Exchange.CONTENT_TYPE; import static org.apache.camel.Exchange.HTTP_METHOD; import static org.apache.camel.Exchange.HTTP_QUERY; import static org.apache.camel.Exchange.HTTP_URI; import static org.apache.camel.builder.PredicateBuilder.and; import static org.apache.camel.builder.PredicateBuilder.in; import static org.apache.camel.builder.PredicateBuilder.not; import static org.apache.camel.builder.PredicateBuilder.or; import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_TYPE; import static org.fcrepo.camel.FcrepoHeaders.FCREPO_RESOURCE_TYPE; import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI; import static org.fcrepo.camel.processor.ProcessorUtils.tokenizePropertyPlaceholder; import static org.slf4j.LoggerFactory.getLogger; import org.apache.camel.LoggingLevel; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.builder.xml.Namespaces; import org.fcrepo.camel.processor.EventProcessor; import org.slf4j.Logger; /** * A content router for handling JMS events. * * @author Aaron Coburn */ public class SolrRouter extends RouteBuilder { private static final Logger logger = getLogger(SolrRouter.class); private static final String hasIndexingTransformation = "(/rdf:RDF/rdf:Description/indexing:hasIndexingTransformation/@rdf:resource | " + "/rdf:RDF/rdf:Description/indexing:hasIndexingTransformation/@rdf:about)[1]"; private static final String RESOURCE_DELETION = "http://fedora.info/definitions/v4/event#ResourceDeletion"; private static final String DELETE = "https://www.w3.org/ns/activitystreams#Delete"; private static final String INDEXING_TRANSFORMATION = "CamelIndexingTransformation"; private static final String INDEXABLE = "http://fedora.info/definitions/v4/indexing#Indexable"; private static final String INDEXING_URI = "CamelIndexingUri"; /** * Configure the message route workflow. */ public void configure() throws Exception { final Namespaces ns = new Namespaces("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); ns.add("indexing", "http://fedora.info/definitions/v4/indexing#"); ns.add("ldp", "http://www.w3.org/ns/ldp#"); /* * A generic error handler (specific to this RouteBuilder) */ onException(Exception.class) .maximumRedeliveries("{{error.maxRedeliveries}}") .log("Index Routing Error: ${routeId}"); /* * route a message to the proper queue, based on whether * it is a DELETE or UPDATE operation. */ from("{{input.stream}}") .routeId("FcrepoSolrRouter") .process(new EventProcessor()) .choice() .when(or(header(FCREPO_EVENT_TYPE).contains(RESOURCE_DELETION), header(FCREPO_EVENT_TYPE).contains(DELETE))) .to("direct:delete.solr") .otherwise() .to("direct:index.solr"); /* * Handle re-index events */ from("{{solr.reindex.stream}}") .routeId("FcrepoSolrReindex") .to("direct:index.solr"); /* * Based on an item's metadata, determine if it is indexable. */ from("direct:index.solr") .routeId("FcrepoSolrIndexer") .removeHeaders("CamelHttp*") .filter(not(in(tokenizePropertyPlaceholder(getContext(), "{{filter.containers}}", ",").stream() .map(uri -> or( header(FCREPO_URI).startsWith(constant(uri + "/")), header(FCREPO_URI).isEqualTo(constant(uri)))) .collect(toList())))) .choice() .when(and(simple("{{indexing.predicate}} != 'true'"), simple("{{fcrepo.checkHasIndexingTransformation}} != 'true'"))) .setHeader(INDEXING_TRANSFORMATION).simple("{{fcrepo.defaultTransform}}") .to("direct:update.solr") .otherwise() .to("fcrepo:{{fcrepo.baseUrl}}?preferOmit=PreferContainment&accept=application/rdf+xml") .setHeader(INDEXING_TRANSFORMATION).xpath(hasIndexingTransformation, String.class, ns) .choice() .when(or(header(INDEXING_TRANSFORMATION).isNull(), header(INDEXING_TRANSFORMATION).isEqualTo(""))) .setHeader(INDEXING_TRANSFORMATION).simple("{{fcrepo.defaultTransform}}").end() .removeHeaders("CamelHttp*") .choice() .when(or(simple("{{indexing.predicate}} != 'true'"), header(FCREPO_RESOURCE_TYPE).contains(INDEXABLE))) .to("direct:update.solr") .otherwise() .to("direct:delete.solr"); /* * Remove an item from the solr index. */ from("direct:delete.solr").routeId("FcrepoSolrDeleter") .removeHeaders("CamelHttp*") .to("mustache:org/fcrepo/camel/indexing/solr/delete.mustache") .log(LoggingLevel.INFO, logger, "Deleting Solr Object ${headers[CamelFcrepoUri]}") .setHeader(HTTP_METHOD).constant("POST") .setHeader(CONTENT_TYPE).constant("application/json") .setHeader(HTTP_QUERY).simple("commitWithin={{solr.commitWithin}}") .to("{{solr.baseUrl}}/update?useSystemProperties=true"); from("direct:external.ldpath").routeId("FcrepoSolrLdpathFetch") .removeHeaders("CamelHttp*") .setHeader(HTTP_URI).header(INDEXING_TRANSFORMATION) .setHeader(HTTP_METHOD).constant("GET") .to("http4://localhost/ldpath"); from("direct:transform.ldpath").routeId("FcrepoSolrTransform") .removeHeaders("CamelHttp*") .setHeader(HTTP_URI).simple("{{ldpath.service.baseUrl}}") .setHeader(HTTP_QUERY).simple("context=${headers.CamelFcrepoUri}") .to("http4://localhost/ldpath"); /* * Handle update operations */ from("direct:update.solr").routeId("FcrepoSolrUpdater") .log(LoggingLevel.INFO, logger, "Indexing Solr Object ${header.CamelFcrepoUri}") .setBody(constant(null)) .setHeader(INDEXING_URI).simple("${header.CamelFcrepoUri}") // Don't index the transformation itself .filter().simple("${header.CamelIndexingTransformation} != ${header.CamelIndexingUri}") .choice() .when(header(INDEXING_TRANSFORMATION).startsWith("http")) .log(LoggingLevel.INFO, logger, "Fetching external LDPath program from ${header.CamelIndexingTransformation}") .to("direct:external.ldpath") .setHeader(HTTP_METHOD).constant("POST") .to("direct:transform.ldpath") .to("direct:send.to.solr") .when(or(header(INDEXING_TRANSFORMATION).isNull(), header(INDEXING_TRANSFORMATION).isEqualTo(""))) .setHeader(HTTP_METHOD).constant("GET") .to("direct:transform.ldpath") .to("direct:send.to.solr") .otherwise() .log(LoggingLevel.INFO, logger, "Skipping ${header.CamelFcrepoUri}"); /* * Send the transformed resource to Solr */ from("direct:send.to.solr").routeId("FcrepoSolrSend") .removeHeaders("CamelHttp*") .setHeader(HTTP_METHOD).constant("POST") .setHeader(HTTP_QUERY).simple("commitWithin={{solr.commitWithin}}") .to("{{solr.baseUrl}}/update?useSystemProperties=true"); } }