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.profiler.service.testing; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.integration.Message; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.channel.RendezvousChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.stereotype.Service; import com.seajas.search.bridge.jms.integration.GroupIdDecorator; import com.seajas.search.bridge.jms.integration.MessageConstants; import com.seajas.search.bridge.jms.model.test.TestElement; import com.seajas.search.bridge.jms.model.test.TestElementImpl; import com.seajas.search.bridge.jms.model.test.TestResult; import com.seajas.search.bridge.jms.model.test.TestType; import com.seajas.search.bridge.profiler.model.feed.Feed; import com.seajas.search.bridge.profiler.model.feed.FeedResultParameter; import com.seajas.search.bridge.profiler.model.feed.FeedUrl; import com.seajas.search.profiler.authentication.strategy.AuthenticationResult; import com.seajas.search.profiler.jms.service.InjectionService; import com.seajas.search.profiler.model.archive.Archive; import com.seajas.search.profiler.model.archive.ArchiveUrl; import com.seajas.search.profiler.service.modifier.ModifierService; import com.seajas.search.profiler.service.profiler.ProfilerService; import com.seajas.search.utilities.logging.SearchLogger; /** * Modifier, feed, archive testing service. * * @author Jasper van Veghel <jasper@seajas.com> */ @Service public class TestingService { /** * The logger. */ @Autowired private SearchLogger logger; /** * The random minimum. */ private static final Integer RANDOM_RANGE_MINIMUM = 0; /** * The modifier service. */ @Autowired private ModifierService modifierService; /** * The testing channel. */ @Autowired private PublishSubscribeChannel testingChannel; /** * The injection service. */ @Autowired private InjectionService injectionService; /** * The profiler service. */ @Autowired private ProfilerService profilerService; /** * The testing timeout */ @Value("${profiler.project.testing.channel.receive.timeout}") private Integer receiveTimeout; /** * The random number generator for random delays and user agents. */ private final Random randomGenerator = new Random(); /** * Return the relevant feed URL by the modifier ID, along with its optional override encodings (feed, result). * * @param modifierId * @return String[] */ public TestElement getTestFeedByModifierId(final Integer modifierId) { Feed testFeed = modifierService.getModifierById(modifierId).getTestFeed(); if (testFeed != null && testFeed.getFeedUrls().size() > 0) { try { if (testFeed.getIsEnabled()) { // Create a map out of the feed result parameters Map<String, String> resultParameters = new HashMap<String, String>(); if (testFeed.getFeedResultParameters() != null && testFeed.getFeedResultParameters().size() > 0) for (FeedResultParameter feedResultParameter : testFeed.getFeedResultParameters()) { if (resultParameters.containsKey(feedResultParameter.getFieldName())) logger.warn("The result map already contains a parameter named '" + feedResultParameter.getFieldName() + "' - it will be overwritten"); resultParameters.put(feedResultParameter.getFieldName(), feedResultParameter.getFieldValue()); } // Perform the optional authentication step URI uri = preparedFeedUri(testFeed, resultParameters, testFeed.getFeedUrls().get(0).getUrl()); return new TestElementImpl(uri, testFeed.getFeedEncodingOverride(), testFeed.getResultEncodingOverride(), getPreferredUserAgentFromFeed(testFeed), modifierId); } else logger.warn("The modifier with ID " + modifierId + " is associated with a disabled test feed - not returning for verification"); } catch (URISyntaxException e) { logger.warn("The modifier with ID " + modifierId + " is associated with a test feed containing an invalid URI - not returning for verification"); } } return null; } /** * Dispatch a feed connection test request and wait for the result. * * @param feed * @return Map<String, Boolean> * @throws TestingException */ public Map<String, Boolean> testFeedConnection(final Feed feed) throws TestingException { Map<String, Boolean> urlResults = new HashMap<String, Boolean>(); // Create a map out of the feed result parameters Map<String, String> resultParameters = new HashMap<String, String>(); if (feed.getFeedResultParameters() != null && feed.getFeedResultParameters().size() > 0) for (FeedResultParameter feedResultParameter : feed.getFeedResultParameters()) { if (resultParameters.containsKey(feedResultParameter.getFieldName())) logger.warn("The result map already contains a parameter named '" + feedResultParameter.getFieldName() + "' - it will be overwritten"); resultParameters.put(feedResultParameter.getFieldName(), feedResultParameter.getFieldValue()); } for (FeedUrl feedUrl : feed.getFeedUrls()) { try { // Perform the optional authentication step URI uri = preparedFeedUri(feed, resultParameters, feedUrl.getUrl()); TestResult result = testModifier(new TestElementImpl(uri, feed.getFeedEncodingOverride(), feed.getResultEncodingOverride(), getPreferredUserAgentFromFeed(feed), null), TestType.CONNECTION); urlResults.put(String.valueOf(feedUrl.getId()), result.isSuccess()); } catch (URISyntaxException e) { urlResults.put(String.valueOf(feedUrl.getId()), false); } } return urlResults; } /** * Dispatch a feed-modifier test request and wait for the result. * * @param testFeed * @return Map<String, Boolean> * @throws TestingException */ public Map<String, Boolean> testFeedModifier(final TestElement testFeed) throws TestingException { TestResult result = testModifier(testFeed, TestType.SHALLOW); if (result.isSuccess()) return result.getResult(); else throw new TestingException(result.getStatusMessage()); } /** * Dispatch a result-modifier test request and wait for the result. * * @param testFeed * @return Map<String, Boolean> * @throws TestingException */ public Map<String, Boolean> testResultModifier(final TestElement testFeed) throws TestingException { TestResult result = testModifier(testFeed, TestType.DEEP); if (result.isSuccess()) return result.getResult(); else throw new TestingException(result.getStatusMessage()); } /** * Dispatch an archive connection test request and wait for the result. * * @param archive * @return Map<String, Boolean> * @throws TestingException */ public Map<String, Boolean> testArchiveConnection(final Archive archive) throws TestingException { Map<String, Boolean> urlResults = new HashMap<String, Boolean>(); for (ArchiveUrl archiveUrl : archive.getArchiveUrls()) { try { TestResult result = testModifier(new TestElementImpl(new URI(archiveUrl.getUrl()), true), TestType.CONNECTION); urlResults.put(String.valueOf(archiveUrl.getId()), result.isSuccess()); } catch (URISyntaxException e) { urlResults.put(String.valueOf(archiveUrl.getId()), false); } } return urlResults; } /** * Dispatch a type-specified modifier test request and wait for the result. * * @param testFeed * @param type * @return Map<String, Boolean> * @throws TestingException */ @SuppressWarnings("unchecked") private TestResult testModifier(final TestElement testFeed, final TestType type) throws TestingException { final UUID sessionUUID = UUID.randomUUID(); final RendezvousChannel temporaryChannel = new RendezvousChannel(); final MessageHandler messageHandler = new MessageHandler() { @Override public void handleMessage(final Message<?> message) { String messageUUID = message.getHeaders().get(MessageConstants.HEADER_RENDEZVOUS_UUID, String.class); if (sessionUUID.toString().equals(messageUUID)) temporaryChannel.send(message); } }; testingChannel.subscribe(messageHandler); try { injectionService.inject(testFeed, GroupIdDecorator.decorate(testFeed.getTestingUri().getHost()), type.name(), sessionUUID.toString()); // Now wait for read out the rendezvous-channel until we receive the response Message<TestResult> message = (Message<TestResult>) temporaryChannel.receive(receiveTimeout); if (message != null) return message.getPayload(); throw new TestingException("Receiving the testing response failed or timed out"); } finally { testingChannel.unsubscribe(messageHandler); } } /** * Dispatch a feed-testing request, where the result is evaluated on the contender-side to be of type RSS - directly or indirectly. * * <ul> * <li>When a TestingException is thrown, something went wrong or no URLs could be found</li> * <li>When one entry with a boolean value of 'true' is returned, the URL concerns a direct RSS feed</li> * <li>When one or more entry with a boolean value of 'false' is returned, the URL concerns an indirect RSS feed with one or more links</li> * </ul> * * @param uri * @return TestResult * @throws TestingException */ @SuppressWarnings("unchecked") public Map<String, Boolean> exploreUri(final URI uri) throws TestingException { final UUID sessionUUID = UUID.randomUUID(); final RendezvousChannel temporaryChannel = new RendezvousChannel(); final MessageHandler messageHandler = new MessageHandler() { @Override public void handleMessage(final Message<?> message) { String messageUUID = message.getHeaders().get(MessageConstants.HEADER_RENDEZVOUS_UUID, String.class); if (sessionUUID.toString().equals(messageUUID)) temporaryChannel.send(message); } }; testingChannel.subscribe(messageHandler); try { TestElement testElement = new TestElementImpl(uri, null, null, null, null); if (logger.isInfoEnabled()) logger.info("Injecting element with exploration URI '" + uri + "'"); injectionService.inject(testElement, GroupIdDecorator.decorate(uri.getHost()), TestType.EXPLORE.name(), sessionUUID.toString()); // Now wait for read out the rendezvous-channel until we receive the response Message<TestResult> message = (Message<TestResult>) temporaryChannel.receive(receiveTimeout); if (message != null) { TestResult result = message.getPayload(); if (!result.isSuccess()) throw new TestingException( "No feeds - direct or indirect - were retrieved: " + result.getStatusMessage()); else return result.getResult(); } throw new TestingException("Test response aborted by timeout or interrupt."); } finally { testingChannel.unsubscribe(messageHandler); } } /** * Retrieve the preferred user agent from the given feed, applying randomization if necessary. * * @param feed * @return String */ private String getPreferredUserAgentFromFeed(final Feed feed) { String preferredUserAgent = feed.getFeedAnonymization() != null && feed.getFeedAnonymization().getPreferredUserAgents() != null && feed.getFeedAnonymization().getPreferredUserAgents().size() > 0 ? feed.getFeedAnonymization().getPreferredUserAgents().get(0) : null; if (feed.getFeedAnonymization() != null && feed.getFeedAnonymization().getPreferredUserAgents() != null && feed.getFeedAnonymization().getPreferredUserAgents().size() > 1 && feed.getFeedAnonymization().getIsPreferredUserAgentsRandomized()) preferredUserAgent = feed.getFeedAnonymization().getPreferredUserAgents().get(randomGenerator.nextInt( (feed.getFeedAnonymization().getPreferredUserAgents().size() - 1) - RANDOM_RANGE_MINIMUM + 1) + RANDOM_RANGE_MINIMUM); return preferredUserAgent; } /** * Does authorization if needed. * FIXME: authentication does not belong here. */ private URI preparedFeedUri(Feed feed, Map<String, String> parameters, String url) throws URISyntaxException { if (feed.getFeedConnection() != null) { if (feed.getFeedConnection().getAuthenticationStrategy() != null) { AuthenticationResult authenticationResult = profilerService.applyAuthenticationStrategy( feed.getFeedConnection().getAuthenticationStrategy(), url, parameters); url = authenticationResult.getUrl(); parameters = authenticationResult.getResultParameters(); } } // Can we skip authentication if there is no connection? return new URI(url); } }