org.alfresco.bm.test.Test.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.bm.test.Test.java

Source

/*
 * Copyright (C) 2005-2013 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */
package org.alfresco.bm.test;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;

import org.alfresco.bm.log.LogService;
import org.alfresco.bm.test.mongo.MongoTestDAO;
import org.alfresco.bm.test.prop.TestProperty;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.types.ObjectId;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.mongodb.DBCursor;
import com.mongodb.DBObject;

/**
 * Implementation of service managing and querying the server instances
 * 
 * @author Derek Hulley
 * @since 2.0
 */
public class Test implements LifecycleListener, TestConstants, ApplicationContextAware {
    /** default test run monitor time */
    private static final long DEFAULT_TEST_RUN_MONITOR_PERIOD = 5000L;

    private static Log logger = LogFactory.getLog(Test.class);

    private ApplicationContext ctx;

    private final MongoTestDAO testDAO;
    private final LogService logService;

    private final String release;
    private final Integer schema;
    private final String description;
    private final String contextPath;
    private final TestDefaults defaults;
    private InetAddress inetAddress;
    private Set<String> systemCapabilities;

    /** The task that keeps the driver details fresh in the database */
    private final TestDriverPingTask refreshRegistrationTask;
    private volatile String driverId;

    /** The task that creates monitors for test runs */
    private final TestRunPingTask testRunPingTask;
    /** The time between test run monitor runs */
    private long testRunMonitorPeriod = DEFAULT_TEST_RUN_MONITOR_PERIOD;

    /**
     * @param testDAO               data persistence
     * @param logService            logging
     * @param release               the software release name of this test
     * @param schema                the property schema version
     * @param description           the test description
     * @param contextPath           the context under which the application was launched
     * @param defaults              provider of all the test defaults
     */
    public Test(MongoTestDAO testDAO, LogService logService, String release, Integer schema, String description,
            String contextPath, TestDefaults defaults) {
        this.testDAO = testDAO;
        this.logService = logService;
        this.release = release;
        this.schema = schema;
        this.description = description;
        this.contextPath = contextPath;
        this.defaults = defaults;
        this.systemCapabilities = Collections.singleton(CAPABILITY_JAVA6);
        this.refreshRegistrationTask = new TestDriverPingTask();
        this.testRunPingTask = new TestRunPingTask();

        // This will only be valid if driver registration succeeds
        this.driverId = null;
    }

    /**
     * Override the {@link #DEFAULT_TEST_RUN_MONITOR_PERIOD default} test run monitor period.
     */
    public void setTestRunMonitorPeriod(long testRunMonitorPeriod) {
        this.testRunMonitorPeriod = testRunMonitorPeriod;
    }

    /**
     * Keep track of the parent application context, which will act as a parent context for all test runs.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    /**
     * Set a comma-separated string of capabilities.  These are tags and can be or represent anything.
     */
    public void setSystemCapabilities(String capabilitiesStr) {
        if (capabilitiesStr == null) {
            throw new IllegalArgumentException("Capabilities string cannot be null.");
        }
        Set<String> capabilitiesTmp = new HashSet<String>(5);
        StringTokenizer st = new StringTokenizer(capabilitiesStr, ",");
        while (st.hasMoreTokens()) {
            String capability = st.nextToken().trim().toLowerCase();
            capabilitiesTmp.add(capability);
        }
        // Prevent later modification
        this.systemCapabilities = Collections.unmodifiableSet(capabilitiesTmp);
    }

    /**
     * @return              the build-supplied release name
     */
    public String getRelease() {
        return release;
    }

    /**
     * @return              the build-supplied schema number
     */
    public Integer getSchema() {
        return schema;
    }

    /**
     * Timer callback that ensures the test driver details are updated and kept alive
     * 
     * @author Derek Hulley
     * @since 2.0
     */
    private class TestDriverPingTask extends TimerTask {
        /** 1 minute registration timeout */
        private static final long PING_TIMEOUT = 1L * 60L * 1000L;

        private boolean active = true; // Start off active

        public synchronized void setActive(boolean active) {
            this.active = active;
        }

        @Override
        public synchronized void run() {
            if (active && driverId != null) {
                // Add the ping
                long expiryTime = System.currentTimeMillis() + PING_TIMEOUT;
                testDAO.refreshDriver(driverId, expiryTime);
            } else {
                // Cancel internally
                super.cancel();
            }
        }
    }

    /**
     * Timer callback that generates test run monitors
     * 
     * @author Derek Hulley
     * @since 2.0
     */
    private class TestRunPingTask extends TimerTask {
        private boolean active = true; // Start off active
        private final Map<ObjectId, TestRun> testRuns;

        /**
         * Internal constructor for containing class only
         */
        private TestRunPingTask() {
            testRuns = new HashMap<ObjectId, TestRun>(23);
        }

        /**
         * Deactivate the ping task, which stops all associated test runs
         */
        public synchronized void deactivate() {
            this.active = false;
            // Call all test runs and remove them
            for (TestRun testRun : testRuns.values()) {
                testRun.stop();
            }
        }

        /**
         * Get the object managing the specific test run
         * 
         * @param testRunId             the ID of the test run
         * @return                      the test run wrapper or <tt>null</tt> if not found
         */
        private synchronized TestRun getTestRun(ObjectId testRunId) {
            return testRuns.get(testRunId);
        }

        @Override
        public synchronized void run() {
            if (!active) {
                // Cancel internally
                super.cancel();
                return;
            }

            // Keep track of all test runs so that we can remove redundant instances
            Set<ObjectId> redundantTestRunIds = new HashSet<ObjectId>(testRuns.keySet());

            int testsSkip = 0;
            DBCursor testsCursor = testDAO.getTests(release, schema, testsSkip, 100);
            testsSkip += testsCursor.count();
            while (testsCursor.hasNext()) {
                DBObject testObj = testsCursor.next();
                String test = (String) testObj.get(FIELD_NAME);
                // For each test (matching the app we're in) get the runs
                int testRunsSkip = 0;
                DBCursor testRunsCursor = testDAO.getTestRuns(test, testRunsSkip, 10, TestRunState.SCHEDULED,
                        TestRunState.STARTED);
                testRunsSkip += testRunsCursor.count();
                while (testRunsCursor.hasNext()) {
                    DBObject testRunObj = testRunsCursor.next();
                    ObjectId testRunId = (ObjectId) testRunObj.get(FIELD_ID);
                    if (testRuns.containsKey(testRunId)) {
                        // Remove it from the redundant list
                        redundantTestRunIds.remove(testRunId);
                        // We have already created a test run for this
                        continue;
                    }
                    // Build a test run
                    TestRun testRun = new TestRun(testDAO, logService, testRunId, ctx, driverId);
                    testRuns.put(testRunId, testRun);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Created TestRun monitor: " + testRunObj);
                    }
                }
                // Close the cursor.  Note that the use of a finally is not necessary as Mongo has protection against leaked cursors.
                testRunsCursor.close();
            }

            // Remove redundant monitors
            for (ObjectId testRunId : redundantTestRunIds) {
                // We make one last call to the test run we are dropping so that it can do a final cleanup
                TestRun testRun = testRuns.get(testRunId);
                testRun.checkState();
                // Now clean it out
                testRuns.remove(testRunId);
                if (logger.isDebugEnabled()) {
                    logger.debug("Removed redundant TestRun monitor: " + testRunId);
                }
            }

            // Call each monitor to get it to check itself
            for (TestRun testRun : testRuns.values()) {
                testRun.checkState();
            }
        }
    }

    /**
     * Get the object managing the specific test run
     * 
     * @param testRunId             the ID of the test run
     * @return                      the test run wrapper or <tt>null</tt> if not found
     */
    public TestRun getTestRun(ObjectId testRunId) {
        if (testRunPingTask == null) {
            return null; // Probably not started
        } else {
            return testRunPingTask.getTestRun(testRunId);
        }
    }

    @Override
    public void start() throws Exception {
        initNetworkDetails();

        // The core BM Server application does NOT drive anything
        boolean isDriver = !release.toLowerCase().startsWith("alfresco-benchmark-server-");
        if (isDriver) {
            // Ensure that there is a representation of the test in the DB
            initTestDefaults();

            // Register this driver
            registerDriver();

            // Store driver details
            refreshRegistrationTask.run();
            // Make sure we keep it refreshed
            Timer timer = new Timer("TestDriverPing-" + release + "-" + schema, true);
            timer.schedule(refreshRegistrationTask, 0L, TestDriverPingTask.PING_TIMEOUT / 2);

            // Create monitors for all test runs of interest
            testRunPingTask.run();
            // Keep it refreshed
            timer = new Timer("TestRunPing-" + release + "-" + schema, true);
            timer.schedule(testRunPingTask, testRunMonitorPeriod, testRunMonitorPeriod);
        } else {
            logger.debug("Not registering driver details: " + release + ", " + schema + ", " + systemCapabilities);
        }
    }

    /**
     * <b>TEST ONLY:</b> Force the driver ping task to execute on the current thread.
     */
    public void forcePing() {
        testRunPingTask.run();
    }

    @Override
    public void stop() throws Exception {
        // Kill the ping
        refreshRegistrationTask.setActive(false);
        if (driverId != null) {
            // Remove driver entry
            testDAO.unregisterDriver(driverId);
        }
        // Kill monitoring
        testRunPingTask.deactivate();
    }

    /**
     * Initialize IP address and hostname
     */
    private void initNetworkDetails() throws Exception {
        // We have some preferences about what to use
        InetAddress ipv4 = null;
        InetAddress multicast = null;
        InetAddress ipv6 = null;

        // Get an IP address and host name
        Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
        while (nis.hasMoreElements()) {
            NetworkInterface ni = nis.nextElement();
            // Check each network interface
            Enumeration<InetAddress> ias = ni.getInetAddresses();
            while (ias.hasMoreElements()) {
                InetAddress ia = ias.nextElement();
                if (ipv4 == null && ia instanceof Inet4Address) {
                    // Our first choice
                    ipv4 = ia;
                } else if (multicast != null && ia.isMulticastAddress()) {
                    multicast = ia;
                } else if (ipv6 != null && ia instanceof Inet6Address) {
                    ipv6 = ia;
                }
            }
        }
        // Now go by preference
        if (ipv4 != null) {
            inetAddress = ipv4;
        } else if (multicast != null) {
            inetAddress = multicast;
        } else if (ipv6 != null) {
            inetAddress = ipv6;
        } else if (inetAddress == null) {
            inetAddress = InetAddress.getLocalHost();
        }
    }

    /**
     * Write test defaults into the persitent storage, taking care that existing values
     * are not modified.
     */
    private void initTestDefaults() throws Exception {
        // Check the test defaults
        if (defaults == null) {
            throw new RuntimeException("No test defaults provided.");
        }
        if (release == null || release.length() == 0) {
            throw new RuntimeException("Test defaults did not provide a release string.");
        }
        if (schema == null || schema.intValue() < 0) {
            throw new RuntimeException("Test defaults did not provide a schema number.");
        }

        List<TestProperty> properties = defaults.getPropertiesList();
        boolean written = testDAO.writeTestDef(release, schema, description, properties);
        if (!written) {
            logger.info("Test definition already written: " + release + ":" + schema);
        }
    }

    /**
     * Ensure that this instance registers as a driver
     */
    private void registerDriver() throws Exception {
        String hostname = inetAddress.getHostName();
        String ipAddress = inetAddress.getHostAddress();

        driverId = testDAO.registerDriver(release, schema, ipAddress, hostname, contextPath, systemCapabilities);

        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Registered test driver details: " + release + ", " + ipAddress + ", " + systemCapabilities);
        }
    }
} //org.alfresco.bm.test.Test