com.seajas.search.contender.jms.endpoint.ProcessorEndpoint.java Source code

Java tutorial

Introduction

Here is the source code for com.seajas.search.contender.jms.endpoint.ProcessorEndpoint.java

Source

/**
 * Copyright (C) 2013 Seajas, the Netherlands.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.seajas.search.contender.jms.endpoint;

import com.seajas.search.bridge.jms.integration.MessageConstants;
import com.seajas.search.bridge.jms.model.Archive;
import com.seajas.search.bridge.jms.model.CompositeEntry;
import com.seajas.search.bridge.jms.model.Feed;
import com.seajas.search.bridge.jms.model.Source;
import com.seajas.search.bridge.jms.model.state.CompositeState;
import com.seajas.search.bridge.jms.model.test.TestElement;
import com.seajas.search.bridge.jms.model.test.TestType;
import com.seajas.search.contender.jms.processor.ArchiveProcessor;
import com.seajas.search.contender.jms.processor.FeedProcessor;
import com.seajas.search.contender.jms.processor.SourceElementProcessor;
import com.seajas.search.contender.jms.processor.TestElementProcessor;
import com.seajas.search.contender.jms.service.InjectionService;
import com.seajas.search.contender.service.cache.CacheService;
import com.seajas.search.contender.service.management.ManagementState;
import com.seajas.search.contender.service.storage.StorageService;
import java.util.EnumSet;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.stereotype.Service;

/**
 * A simple processor endpoint.
 *
 * @author Jasper van Veghel <jasper@seajas.com>
 */
@Service
public class ProcessorEndpoint {
    /**
     * The logger.
     */
    private static final Logger logger = LoggerFactory.getLogger(ProcessorEndpoint.class);

    /**
     * The archive processor.
     */
    @Autowired
    private ArchiveProcessor archiveProcessor;

    /**
     * The feed processor.
     */
    @Autowired
    private FeedProcessor feedProcessor;

    /**
     * The source element processor.
     */
    @Autowired
    private SourceElementProcessor sourceElementProcessor;

    /**
     * The test element processor.
     */
    @Autowired
    private TestElementProcessor testElementProcessor;

    /**
     * The cache service.
     */
    @Autowired
    private CacheService cacheService;

    /**
     * The processing cache (local)
     */
    @Autowired
    private Ehcache processingCache;

    /**
     * The storage service.
     */
    @Autowired
    private StorageService storageService;

    /**
     * The injection service.
     */
    @Autowired
    private InjectionService injectionService;

    /**
     * Process the incoming primary message and delegate it according to its payload.
     *
     * @param message
     */
    @ServiceActivator(inputChannel = "primaryProcessingChannel")
    public void processPrimary(final Message<Source> message) {
        ManagementState.getTypedEntries(message.getPayload()).incrementCurrentMinute();

        try {
            Source source = message.getPayload();

            // Check it against the processing cache

            String cacheKey = cacheService.createCompositeKey(source.getUri().toString(),
                    source.getResultParameters());

            if (!handleMessageProcessing(cacheKey))
                return;

            // This is where we handle Feeds, Archives, and SourceElements

            try {
                if (message.getPayload() instanceof Feed)
                    feedProcessor.process((Feed) source);
                else if (message.getPayload() instanceof Archive)
                    archiveProcessor.process((Archive) source);
                else
                    throw new IllegalArgumentException(
                            "Payload in primary queue is of unknown instance type - must be one of Feed or Archive");
            } finally {
                finalizeMessageProcessing(cacheKey);
            }
        } catch (RuntimeException e) {
            logger.error("Uncaught runtime exception", e);

            throw e;
        }
    }

    /**
     * Process the incoming secondary message and delegate it to the SourceElement or reindexing processor.
     *
     * @param message
     */
    @ServiceActivator(inputChannel = "secondaryProcessingChannel")
    public void processSecondary(final Message<ObjectId> message) {
        CompositeEntry entry = storageService.retrieveEntryById(message.getPayload());

        try {
            if (entry == null)
                throw new IllegalArgumentException("The given composite entry does not exist");

            ManagementState.getTypedEntries(entry).incrementCurrentMinute();

            if (Boolean.TRUE.equals(message.getHeaders().get(MessageConstants.HEADER_CHANNEL_REINDEXING))) {
                // The processToModified call should reset the entry-state back to 'Content'

                if (EnumSet.of(CompositeState.Content, CompositeState.InitialDocument,
                        CompositeState.CompletedDocument).contains(entry.getCurrentState())) {
                    if (entry.getFailureState() != null)
                        logger.warn(String.format(
                                "Trying to reindex element with ID %s - which has failed before at state %s",
                                entry.getId().toString(), entry.getFailureState().name()));

                    if (entry.getOriginalContent() != null) {
                        if (logger.isInfoEnabled())
                            logger.info(String.format(
                                    "Reindexing element with ID '%s', state '%s', and failure state %s",
                                    entry.getId(), entry.getCurrentState(),
                                    entry.getFailureState() != null ? "'" + entry.getFailureState() + "'"
                                            : "(none)"));

                        sourceElementProcessor.processToModified(entry,
                                entry.getOriginalContent().getDateSubmitted());

                        storageService.saveEntry(entry);

                        injectionService.injectContent(entry.getId());
                    } else {
                        // Can be explained if the failure state has been set

                        if (entry.getFailureState() == null)
                            throw new IllegalStateException(
                                    "The document destined for re-indexing should contain an original content descriptor - but doesn't");
                    }
                } else
                    throw new IllegalArgumentException(
                            "The document destined for re-indexing is not of type \"Content\", \"InitialDocument\" or \"CompletedDocument\"");
            } else {
                String cacheKey = cacheService.createCompositeKey(entry.getElement().getUri().toString(),
                        entry.getSource().getResultParameters());

                // Check this element against the cache (processing and regular)

                if (!cacheService.isCached(cacheKey)) {
                    // Place it into the processing cache (and remove it again through finalizeMessageProcessing())

                    processingCache.put(new Element(cacheKey, null));

                    try {
                        // This is where we handle Feeds, Archives, and SourceElements

                        if (sourceElementProcessor.processToOriginal(entry)) {
                            // And add the modified content, which we separate like this for the purpose of allowing re-indexing to skip this step

                            if (entry.getOriginalContent() != null && entry.getFailureState() == null) {
                                sourceElementProcessor.processToModified(entry,
                                        entry.getOriginalContent().getDateSubmitted());

                                storageService.saveEntry(entry);

                                if (entry.getOriginalContent().getId() == null)
                                    logger.error(
                                            "Not injecting entry for enriching purposes - content apparently incorrectly stored - no ID was set on it");
                                else
                                    injectionService.injectContent(entry.getId());
                            } else {
                                storageService.saveEntry(entry);

                                if (entry.getFailureState() != null)
                                    logger.error(
                                            "Not injecting entry for enriching purposes - failure state has been set to "
                                                    + entry.getFailureState());
                                else if (entry.getOriginalContent() == null)
                                    logger.error(
                                            "Not injecting entry for enriching purposes - original content has not been set or retrieved (without a failure state or error - weird)");
                            }
                        } else {
                            logger.error("Not injecting entry for enriching purposes - original processing failed");

                            storageService.saveEntry(entry);
                        }
                    } finally {
                        finalizeMessageProcessing(cacheKey);
                    }
                } else
                    logger.warn("[" + cacheKey
                            + "] The entry (by its composite key) being referred to has already been cached - this should (almost) never happen as the cache is already checked prior to injection.");
            }
        } catch (RuntimeException e) {
            logger.error("Uncaught runtime exception", e);

            if (entry.getFailureState() == null) {
                entry.setFailureState(CompositeState.Content);

                storageService.saveEntry(entry);
            }

            throw e;
        } finally {
            if (entry != null)
                storageService.closeEntry(entry);
        }
    }

    /**
     * Process the incoming tertiary message and delegate it according to its payload.
     *
     * @param message
     */
    @ServiceActivator(inputChannel = "tertiaryProcessingChannel")
    public void processTertiary(final Message<TestElement> message) {
        ManagementState.getTypedEntries(message.getPayload()).incrementCurrentMinute();

        try {
            TestType testType = TestType
                    .valueOf(message.getHeaders().get(MessageConstants.HEADER_CHANNEL_TESTTYPE, String.class));
            String rendezvousId = message.getHeaders().get(MessageConstants.HEADER_RENDEZVOUS_UUID, String.class);

            if (testType == null)
                throw new IllegalArgumentException(
                        "The given test message does not contain a test type used to indicate the URL depth - ignoring");
            if (rendezvousId == null)
                throw new IllegalArgumentException(
                        "The given test message does not contain a rendezvous ID used to identify the response - ignoring");

            testElementProcessor.process((TestElement) message.getPayload(), testType, rendezvousId);
        } catch (RuntimeException e) {
            logger.error("Uncaught runtime exception", e);

            throw e;
        }
    }

    /**
     * Do processing interception here, instead of in an interceptor, so that we can opt to simply drop the message altogether
     *
     * @param compositeKey
     * @return boolean
     */
    private boolean handleMessageProcessing(final String compositeKey) {
        if (processingCache.isKeyInCache(compositeKey)) {
            logger.warn("[" + compositeKey
                    + "] The (composite) key being referred to is already being processed, and will be discarded.");

            return false;
        }

        processingCache.put(new Element(compositeKey, null));

        return true;
    }

    /**
     * Finalize processing interception by removing it from the processing cache again.
     *
     * @param compositeKey
     */
    private void finalizeMessageProcessing(final String compositeKey) {
        processingCache.remove(compositeKey);
    }
}