Java tutorial
/** * 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); } }