com.seajas.search.profiler.service.testing.TestingService.java Source code

Java tutorial

Introduction

Here is the source code for com.seajas.search.profiler.service.testing.TestingService.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.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);
    }

}