Java tutorial
/* * Copyright (C) 2005-2014 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.mongo; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.alfresco.bm.api.AESCipher; import org.alfresco.bm.api.v1.ImportResult; import org.alfresco.bm.exception.CipherException; import org.alfresco.bm.exception.ObjectNotFoundException; import org.alfresco.bm.test.LifecycleListener; import org.alfresco.bm.test.TestConstants; import org.alfresco.bm.test.TestRunState; import org.alfresco.bm.test.prop.CipherVersion; import org.alfresco.bm.test.prop.TestProperty; import org.alfresco.bm.test.prop.TestPropertyOrigin; import org.alfresco.bm.util.ArgumentCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.types.ObjectId; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.DuplicateKeyException; import com.mongodb.MongoException; import com.mongodb.QueryBuilder; import com.mongodb.WriteConcern; import com.mongodb.WriteResult; /** * MongoDB persistence of test metadata * * @author Derek Hulley * @author Frank Becker * * @since 2.0 */ public class MongoTestDAO implements LifecycleListener, TestConstants { public static final String COLLECTION_TEST_DEFS = "test.defs"; public static final String COLLECTION_TEST_DRIVERS = "test.drivers"; public static final String COLLECTION_TESTS = "tests"; public static final String COLLECTION_TEST_PROPS = "test.props"; public static final String COLLECTION_TEST_RUNS = "test.runs"; private static Log logger = LogFactory.getLog(MongoTestDAO.class); private final Map<String, TestDefEntry> testDefCache; private final ReentrantReadWriteLock testDefCacheLock; private final DB db; private final DBCollection testDrivers; private final DBCollection testDefs; private final DBCollection tests; private final DBCollection testRuns; private final DBCollection testProps; /** * Construct the DAO using the Mongo DB directly */ public MongoTestDAO(DB db) { ArgumentCheck.checkMandatoryObject(db, "db"); this.testDefCache = new HashMap<String, TestDefEntry>(17); this.testDefCacheLock = new ReentrantReadWriteLock(); this.db = db; this.testDrivers = db.getCollection(COLLECTION_TEST_DRIVERS); this.testDefs = db.getCollection(COLLECTION_TEST_DEFS); this.tests = db.getCollection(COLLECTION_TESTS); this.testRuns = db.getCollection(COLLECTION_TEST_RUNS); this.testProps = db.getCollection(COLLECTION_TEST_PROPS); } /** * @return the database being used */ public DB getDb() { return db; } /** * Initialize indexes */ @Override public void start() throws Exception { // Clean up old data and indexes String[] indexesToDrop = new String[] { "TESTS_NAME_RELEASE", "TEST_DRIVERS_UNIQUE_RELEASE_SCHEMA_IPADDRESS", "TEST_RUNS_TEST", "TEST_RUNS_TEST_NAME_SCHEDULED", "TEST_PROPS_TEST_NAME", "TEST_DEFS_UNIQUE_RELEASE", "TEST_DEFS_RELEASE_SCHEMA", "TEST_DRIVERS_IDX_NAME_EXPIRES" }; for (String indexToDrop : indexesToDrop) { try { testDrivers.dropIndex(indexToDrop); } catch (MongoException e) { // ignore } try { testDefs.dropIndex(indexToDrop); } catch (MongoException e) { // ignore } try { tests.dropIndex(indexToDrop); } catch (MongoException e) { // ignore } try { testRuns.dropIndex(indexToDrop); } catch (MongoException e) { // ignore } try { testProps.dropIndex(indexToDrop); } catch (MongoException e) { // ignore } } // @since 2.0 DBObject idx_TEST_DRIVERS_RELEASE_SCHEMA_IPADDRESS = BasicDBObjectBuilder.start(FIELD_RELEASE, 1) .add(FIELD_SCHEMA, 1).add(FIELD_IP_ADDRESS, 1).get(); DBObject opts_TEST_DRIVERS_RELEASE_SCHEMA_IPADDRESS = BasicDBObjectBuilder.start() .add("name", "TEST_DRIVERS_RELEASE_SCHEMA_IPADDRESS").add("unique", Boolean.FALSE).get(); testDrivers.createIndex(idx_TEST_DRIVERS_RELEASE_SCHEMA_IPADDRESS, opts_TEST_DRIVERS_RELEASE_SCHEMA_IPADDRESS); // @since 2.0 DBObject idx_TEST_DRIVERS_IDX_NAME_EXPIRES_SCHEMA_RELEASE = BasicDBObjectBuilder.start() .add(FIELD_PING + "." + FIELD_EXPIRES, -1).add(FIELD_SCHEMA, 1).add(FIELD_RELEASE, 1).get(); DBObject opts_TEST_DRIVERS_IDX_NAME_EXPIRES_SCHEMA_RELEASE = BasicDBObjectBuilder.start() .add("name", "TEST_DRIVERS_IDX_NAME_EXPIRES_SCHEMA_RELEASE").add("unique", Boolean.FALSE).get(); testDrivers.createIndex(idx_TEST_DRIVERS_IDX_NAME_EXPIRES_SCHEMA_RELEASE, opts_TEST_DRIVERS_IDX_NAME_EXPIRES_SCHEMA_RELEASE); // @since 2.0 DBObject idx_TEST_DEFS_UNIQUE_RELEASE_SCHEMA = BasicDBObjectBuilder.start(FIELD_RELEASE, 1) .add(FIELD_SCHEMA, 1).get(); DBObject opts_TEST_DEFS_UNIQUE_RELEASE_SCHEMA = BasicDBObjectBuilder.start() .add("name", "TEST_DEFS_UNIQUE_RELEASE_SCHEMA").add("unique", Boolean.TRUE).get(); testDefs.createIndex(idx_TEST_DEFS_UNIQUE_RELEASE_SCHEMA, opts_TEST_DEFS_UNIQUE_RELEASE_SCHEMA); // @since 2.0 DBObject idx_TESTS_UNIQUE_NAME = BasicDBObjectBuilder.start(FIELD_NAME, 1).get(); DBObject opts_TESTS_UNIQUE_NAME = BasicDBObjectBuilder.start().add("name", "TESTS_UNIQUE_NAME") .add("unique", Boolean.TRUE).get(); tests.createIndex(idx_TESTS_UNIQUE_NAME, opts_TESTS_UNIQUE_NAME); // @since 2.0 DBObject idx_TESTS_NAME_RELEASE_SCHEMA = BasicDBObjectBuilder.start(FIELD_NAME, 1).add(FIELD_RELEASE, 1) .add(FIELD_SCHEMA, 1).get(); DBObject opts_TESTS_NAME_RELEASE_SCHEMA = BasicDBObjectBuilder.start() .add("name", "TESTS_NAME_RELEASE_SCHEMA").add("unique", Boolean.FALSE).get(); tests.createIndex(idx_TESTS_NAME_RELEASE_SCHEMA, opts_TESTS_NAME_RELEASE_SCHEMA); // @since 2.0 DBObject idx_TEST_RUNS_NAME = BasicDBObjectBuilder.start(FIELD_NAME, 1).get(); DBObject opts_TEST_RUNS_NAME = BasicDBObjectBuilder.start().add("name", "TEST_RUNS_NAME") .add("unique", Boolean.FALSE).get(); testRuns.createIndex(idx_TEST_RUNS_NAME, opts_TEST_RUNS_NAME); // @since 2.0 DBObject idx_TEST_RUNS_UNIQUE_TEST_NAME = BasicDBObjectBuilder.start(FIELD_TEST, 1).add(FIELD_NAME, 1) .get(); DBObject opts_TEST_RUNS_UNIQUE_TEST_NAME = BasicDBObjectBuilder.start() .add("name", "TEST_RUNS_UNIQUE_TEST_NAME").add("unique", Boolean.TRUE).get(); testRuns.createIndex(idx_TEST_RUNS_UNIQUE_TEST_NAME, opts_TEST_RUNS_UNIQUE_TEST_NAME); // @since 2.0 DBObject idx_TEST_RUNS_TEST_STATE_SCHEDULED = BasicDBObjectBuilder.start(FIELD_TEST, 1).add(FIELD_STATE, 1) .add(FIELD_SCHEDULED, 1).get(); DBObject opts_TEST_RUNS_TEST_STATE_SCHEDULED = BasicDBObjectBuilder.start() .add("name", "TEST_RUNS_TEST_STATE_SCHEDULED").add("unique", Boolean.FALSE).get(); testRuns.createIndex(idx_TEST_RUNS_TEST_STATE_SCHEDULED, opts_TEST_RUNS_TEST_STATE_SCHEDULED); // @since 2.0 DBObject idx_TEST_PROPS_UNIQUE_TEST_NAME = BasicDBObjectBuilder.start(FIELD_TEST, 1).add(FIELD_RUN, 1) .add(FIELD_NAME, 1).get(); DBObject opts_TEST_PROPS_UNIQUE_TEST_NAME = BasicDBObjectBuilder.start() .add("name", "TEST_PROPS_UNIQUE_TEST_NAME").add("unique", Boolean.TRUE).get(); testProps.createIndex(idx_TEST_PROPS_UNIQUE_TEST_NAME, opts_TEST_PROPS_UNIQUE_TEST_NAME); } @Override public void stop() throws Exception { } /** * Register a test driver * * @param release * the software release version * @param schema * the schema number * @param ipAddress * the IP address of the machine the application is running on * @param contextPath * the application context path (or similar) for information * @param capabilities * the features supported by the driver * @return a unique registration key */ public String registerDriver(String release, Integer schema, String ipAddress, String hostname, String contextPath, Set<String> capabilities) { DBObject insertObj = BasicDBObjectBuilder.start().add(FIELD_RELEASE, release).add(FIELD_SCHEMA, schema) .add(FIELD_IP_ADDRESS, ipAddress).add(FIELD_HOSTNAME, hostname).add(FIELD_CONTEXT_PATH, contextPath) .add(FIELD_CAPABILITIES, BasicDBObjectBuilder.start().add(FIELD_SYSTEM, capabilities).get()) .add(FIELD_PING, BasicDBObjectBuilder.start().add(FIELD_TIME, new Date()) .add(FIELD_EXPIRES, new Date(0L)).get()) .get(); testDrivers.insert(insertObj); // Get the object ID ObjectId objId = (ObjectId) insertObj.get(FIELD_ID); String id = objId == null ? null : objId.toString(); // Done if (logger.isDebugEnabled()) { // Retrieve the object for debug logger.debug("Registered test driver: \n" + " ID: " + id + "\n" + " New: " + insertObj); } return id; } /** * Refresh the expiry time of a driver * * @param id * the driver id * @param expiryTime * the new expiry time */ public void refreshDriver(String id, long expiryTime) { DBObject queryObj = QueryBuilder.start().and(FIELD_ID).is(new ObjectId(id)).get(); DBObject updateObj = BasicDBObjectBuilder.start().push("$set") .add(FIELD_PING + "." + FIELD_EXPIRES, new Date(expiryTime)).pop().get(); testDrivers.findAndModify(queryObj, null, null, false, updateObj, false, false); // Done if (logger.isDebugEnabled()) { logger.debug("Updated test driver expiry: \n" + " ID: " + id + "\n" + " New: " + expiryTime); } } /** * Unregister a test driver * * @param id * the ID of the registration */ public void unregisterDriver(String id) { // Find the driver by ID DBObject queryObj = BasicDBObjectBuilder.start().add(FIELD_ID, new ObjectId(id)).get(); testDrivers.remove(queryObj); // Done if (logger.isDebugEnabled()) { logger.debug("Unregistered test driver: " + id); } } /** * Get registered test drivers * * @param release * the release name of the test or <tt>null</tt> for all releases * @param schema * the schema number of the driver or <tt>null</tt> for all schemas * @param liveOnly * <tt>true</tt> to retrieve only live instances */ public DBCursor getDrivers(String release, Integer schema, boolean active) { QueryBuilder queryBuilder = QueryBuilder.start(); if (release != null) { queryBuilder.and(FIELD_RELEASE).is(release); } if (schema != null) { queryBuilder.and(FIELD_SCHEMA).is(schema); } if (active) { queryBuilder.and(FIELD_PING + "." + FIELD_EXPIRES).greaterThan(new Date()); } DBObject queryObj = queryBuilder.get(); DBObject sortObj = BasicDBObjectBuilder.start().add(FIELD_RELEASE, 1).add(FIELD_SCHEMA, 1).get(); DBCursor cursor = testDrivers.find(queryObj).sort(sortObj); // Done if (logger.isDebugEnabled()) { logger.debug("Retrieved test driver: \n" + " Release: " + release + "\n" + " Schema: " + schema + "\n" + " active: " + active + "\n" + " Results: " + cursor.count()); } return cursor; } /** * Count registered drivers * * @param release * the release name of the test or <tt>null</tt> for all releases * @param schema * the schema number of the driver or <tt>null</tt> for all schemas * @param liveOnly * <tt>true</tt> to retrieve only live instances * @return a count of the number of drivers matching the criteria */ public long countDrivers(String release, Integer schema, boolean active) { QueryBuilder queryBuilder = QueryBuilder.start(); if (release != null) { queryBuilder.and(FIELD_RELEASE).is(release); } if (schema != null) { queryBuilder.and(FIELD_SCHEMA).is(schema); } if (active) { queryBuilder.and(FIELD_PING + "." + FIELD_EXPIRES).greaterThan(new Date()); } DBObject queryObj = queryBuilder.get(); long count = testDrivers.count(queryObj); // Done if (logger.isDebugEnabled()) { logger.debug("Retrieved test driver: \n" + " Release: " + release + "\n" + " Schema: " + schema + "\n" + " active: " + active + "\n" + " Results: " + count); } return count; } /** * Write all the test application's property definitions against the release * and schema number * * @param release * the test release name * @param schema * the property schema * @param description * a description of the test definition * @param testProperties * the property definitions * @return <tt>true</tt> if the properties were written or <tt>false</tt> * if they already existed */ public boolean writeTestDef(String release, Integer schema, String description, List<TestProperty> testProperties) { // Check the schema number for any existing instance DBObject queryObjExistingSchema = QueryBuilder.start().and(FIELD_RELEASE).is(release).and(FIELD_SCHEMA) .greaterThanEquals(schema).get(); DBObject fieldsObjExistingSchema = BasicDBObjectBuilder.start().add(FIELD_SCHEMA, true).get(); DBObject resultsObjExistingSchema = testDefs.findOne(queryObjExistingSchema, fieldsObjExistingSchema); Integer existingSchema = (resultsObjExistingSchema == null) ? null : (Integer) resultsObjExistingSchema.get(FIELD_SCHEMA); if (existingSchema == null) { if (logger.isDebugEnabled()) { logger.debug("No test definition exists for " + release + ":" + schema); } // Fall through to write the test definition } else if (existingSchema.equals(schema)) { // We have an exact match. Don't do anything. if (logger.isDebugEnabled()) { logger.debug("Test definition exists for " + release + ":" + schema); } return false; } else { // The query found an instance with a larger schema number. We // don't run downgrades on the same release. throw new RuntimeException( "The current test is out of date and needs to be upgraded to a later version or schema " + release + ":" + schema); } // Pattern for valid property names Pattern pattern = Pattern.compile(PROP_NAME_REGEX); // Build a DB-safe map for direct persistence Collection<Properties> testPropertiesForDb = new ArrayList<Properties>(testProperties.size()); for (TestProperty testProperty : testProperties) { // Do not write properties with invalid names Matcher matcher = pattern.matcher(testProperty.getName()); if (!matcher.matches()) { logger.warn("Property will be ignored. The name is non-standard: " + matcher); continue; } // Convert the Java object to Java Properties Properties propValues = testProperty.toProperties(); // That's it. Add it the properties. testPropertiesForDb.add(propValues); } // Attempt an insert DBObject newObj = BasicDBObjectBuilder.start().add(FIELD_RELEASE, release).add(FIELD_SCHEMA, schema) .add(FIELD_DESCRIPTION, description).add(FIELD_PROPERTIES, testPropertiesForDb).get(); try { WriteResult result = testDefs.insert(newObj); if (logger.isDebugEnabled()) { logger.debug("Created test definition: " + result + "\n" + " Release: " + release + "\n" + " Schema: " + schema + "\n" + " New: " + newObj); } return true; } catch (DuplicateKeyException e) { // Already present if (logger.isDebugEnabled()) { logger.debug("Test definition exists for " + release + ":" + schema); } return false; } } /** * @param count * the number of results to retrieve (must be greater than zero) * @return a list of tests, active or all */ public DBCursor getTestDefs(boolean active, int skip, int count) { if (count < 1) { throw new IllegalArgumentException("'count' must be larger than zero."); } DBObject fieldsObj = BasicDBObjectBuilder.start().add(FIELD_RELEASE, true).add(FIELD_SCHEMA, true).get(); DBObject sortObj = BasicDBObjectBuilder.start().add(FIELD_RELEASE, 1).add(FIELD_SCHEMA, 1).get(); DBCursor cursor; if (active) { DBObject queryObj = QueryBuilder.start().put(FIELD_PING + "." + FIELD_EXPIRES) .greaterThanEquals(new Date()).get(); cursor = testDrivers.find(queryObj, fieldsObj).sort(sortObj).skip(skip).limit(count); } else { cursor = testDefs.find(null, fieldsObj).sort(sortObj).skip(skip).limit(count); } // Done if (logger.isDebugEnabled()) { logger.debug("Fetching test definitions: \n" + " active: " + active + "\n" + " skip: " + skip + "\n" + " count: " + count + "\n" + " Results: " + cursor.count()); } return cursor; } /** * Get the test definition for internal use * * @return the test definition (untouched) or <tt>null</tt> */ private DBObject getTestDefRaw(String release, Integer schema) { DBObject queryObj = BasicDBObjectBuilder.start().add(FIELD_RELEASE, release).add(FIELD_SCHEMA, schema) .get(); DBObject testDefObj = testDefs.findOne(queryObj); // Done return testDefObj; } /** * A cacheable object for holding the test definition * * @author Derek Hulley * @since 2.0 */ private static class TestDefEntry { public final DBObject testDefObj; public final Map<String, DBObject> testDefPropsMap; public TestDefEntry(DBObject testDefObj) { this.testDefObj = testDefObj; // Extract the properties Map<String, DBObject> testDefPropsMap = new HashMap<String, DBObject>(29); BasicDBList dbList = (BasicDBList) testDefObj.get(FIELD_PROPERTIES); for (Object dbListObj : dbList) { DBObject propObj = (DBObject) dbListObj; propObj.put(FIELD_VERSION, Integer.valueOf(0)); propObj.put(FIELD_ORIGIN, TestPropertyOrigin.DEFAULTS.name()); String propName = (String) propObj.get(FIELD_NAME); testDefPropsMap.put(propName, propObj); } this.testDefPropsMap = Collections.unmodifiableMap(testDefPropsMap); } } /** * Retrieve a cached version of the test definition * * @return a cached test definition or <tt>null</tt> if not found */ private TestDefEntry getTestDefCached(String release, Integer schema) { String schemaKey = release + "-" + schema; // The common case: read testDefCacheLock.readLock().lock(); try { TestDefEntry entry = testDefCache.get(schemaKey); if (entry != null) { return entry; } } finally { testDefCacheLock.readLock().unlock(); } // Uncommon case: write testDefCacheLock.writeLock().lock(); try { // No need for a double-check, we don't care DBObject testDefObj = getTestDefRaw(release, schema); if (testDefObj == null) { // We won't cache nulls return null; } TestDefEntry entry = new TestDefEntry(testDefObj); testDefCache.put(schemaKey, entry); return entry; } finally { testDefCacheLock.writeLock().unlock(); } } /** * Retrieve a specific (and full) test definition * * @param release * the test definition software release * @param schema * the schema number * @return the test definition or <tt>null</tt> if not found */ public DBObject getTestDef(String release, Integer schema) { TestDefEntry testDefEntry = getTestDefCached(release, schema); DBObject testDefObj = testDefEntry == null ? null : testDefEntry.testDefObj; // Done if (logger.isDebugEnabled()) { logger.debug("Fetched test definition: \n" + " Release: " + release + "\n" + " Schema: " + schema + "\n" + " Results: " + testDefObj); } return testDefObj; } /** * Get a list of all defined tests * * @param release * the test definition software release or <tt>null</tt> for all test * releases * @param schema * the schema number or <tt>null</tt> for all schemas * @return all the currently-defined tests */ public DBCursor getTests(String release, Integer schema, int skip, int count) { BasicDBObjectBuilder queryObjBuilder = BasicDBObjectBuilder.start(); if (release != null && release.length() > 0) { queryObjBuilder.add(FIELD_RELEASE, release); } if (schema != null) { queryObjBuilder.add(FIELD_SCHEMA, schema); } DBObject queryObj = queryObjBuilder.get(); // We don't want everything just now DBObject fieldsObj = BasicDBObjectBuilder.start().add(FIELD_NAME, true).add(FIELD_VERSION, true) .add(FIELD_DESCRIPTION, true).add(FIELD_RELEASE, true).add(FIELD_SCHEMA, true).get(); DBCursor dbCursor = tests.find(queryObj, fieldsObj).skip(skip).limit(count); // Done if (logger.isDebugEnabled()) { logger.debug("Fetched tests: \n" + " Release: " + release + "\n" + " Schema: " + schema + "\n" + " Results: " + dbCursor.count()); } return dbCursor; } /** * Fetch the low-level ID for a test run * * @return the test ID or <tt>null</tt> if not found */ private ObjectId getTestId(String test) { DBObject queryObj = QueryBuilder.start().and(FIELD_NAME).is(test).get(); DBObject fieldsObj = BasicDBObjectBuilder.start().add(FIELD_ID, true).get(); DBObject testObj = tests.findOne(queryObj, fieldsObj); ObjectId testObjId = null; if (testObj != null) { testObjId = (ObjectId) testObj.get(FIELD_ID); } // Done if (logger.isTraceEnabled()) { logger.trace("Fetched test ID: \n" + " Test: " + test + "\n" + " Result: " + testObjId); } return testObjId; } /** * Retrieve the data for given test * * @param testObjId * the ID of the test * @param includeProperties * <tt>true</tt> to flesh out the properties * @return the test object or <tt>null</tt> if not found */ public DBObject getTest(ObjectId testObjId, boolean includeProperties) { DBObject queryObj = QueryBuilder.start(FIELD_ID).is(testObjId).get(); BasicDBObjectBuilder fieldsObjBuilder = BasicDBObjectBuilder.start(FIELD_NAME, 1).add(FIELD_VERSION, true) .add(FIELD_DESCRIPTION, true).add(FIELD_RELEASE, true).add(FIELD_SCHEMA, true); DBObject fieldsObj = fieldsObjBuilder.get(); DBObject testObj = tests.findOne(queryObj, fieldsObj); if (testObj == null) { // The test run no longer exists logger.warn("Test not found. Returning null test: " + testObjId); return null; } BasicDBList propsList = new BasicDBList(); if (includeProperties) { // Get the associated test definition String test = (String) testObj.get(FIELD_NAME); String release = (String) testObj.get(FIELD_RELEASE); Integer schema = (Integer) testObj.get(FIELD_SCHEMA); TestDefEntry testDefEntry = getTestDefCached(release, schema); if (testDefEntry == null) { // Again, we don't bother trying to resolve this logger.warn("Test definition not found for test: " + testObj); logger.warn("Deleting test without a test definition: " + testObj); this.deleteTest(test); return null; } else { // Start with the properties from the test definition Map<String, DBObject> propsMap = new HashMap<String, DBObject>(testDefEntry.testDefPropsMap); // Fetch the properties for the test DBCursor testPropsCursor = getTestPropertiesRaw(testObjId, null); // Combine MongoTestDAO.mergeProperties(propsMap, testPropsCursor); // Turn into a map and add back into the object propsList = MongoTestDAO.getPropertyList(propsMap); testObj.put(FIELD_PROPERTIES, propsList); } } // Done if (logger.isDebugEnabled()) { logger.debug("Found test: " + testObj); } return testObj; } /** * Retrieve the data for given test * * @param test * the test name * @param includeProperties * <tt>true</tt> to flesh out the properties * @return the test object or <tt>null</tt> if not found */ public DBObject getTest(String test, boolean includeProperties) { ObjectId testObjId = getTestId(test); if (testObjId == null) { // The test run no longer exists logger.warn("Test not found. Returning null test: " + test); return null; } return getTest(testObjId, includeProperties); } /** * Create a new test by copying an existing test. * <p/> * All property overrides will be copied, which is where the value really * lies. * Test runs are not copied. * * @param name * a globally-unique name using * {@link ConfigConstants#TEST_NAME_REGEX} * @param release * the test definition software release or <tt>null</tt> to use the * same release as the source test * @param schema * the schema number or <tt>null</tt> to use the same schema as the * source test * @param copyOfTest * the test name to copy * @param copyOfVersion * the version of the test to copy * @return <tt>true</tt> if the test was copied or <tt>false</tt> if not */ public boolean copyTest(String test, String release, Integer schema, String copyOfTest, int copyOfVersion) { // Get the test DBObject copyOfTestObj = getTest(copyOfTest, true); if (copyOfTestObj == null || !Integer.valueOf(copyOfVersion).equals(copyOfTestObj.get(FIELD_VERSION))) { logger.warn("Did not find test to copy: " + test + " (V" + copyOfVersion + ")"); return false; } if (release == null) { // Use the source test release release = (String) copyOfTestObj.get(FIELD_RELEASE); } if (schema == null) { schema = (Integer) copyOfTestObj.get(FIELD_SCHEMA); } String description = (String) copyOfTestObj.get(FIELD_DESCRIPTION); // Copy the test if (!createTest(test, description, release, schema)) { logger.warn("Failed to create a test via copy: " + test); return false; } // Get the properties to copy BasicDBList copyOfPropObjs = (BasicDBList) copyOfTestObj.get(FIELD_PROPERTIES); if (copyOfPropObjs == null) { copyOfPropObjs = new BasicDBList(); } for (Object obj : copyOfPropObjs) { DBObject copyPropObj = (DBObject) obj; Integer copyPropVer = (Integer) copyPropObj.get(FIELD_VERSION); if (copyPropVer == null || copyPropVer.intValue() <= 0) { // There has been no override continue; } String propName = (String) copyPropObj.get(FIELD_NAME); // Is this property present in the new test (might be copying // between releases) if (getProperty(test, null, propName) == null) { // The new test does not have the property so do not import the // overridden value continue; } String propValue = (String) copyPropObj.get(FIELD_VALUE); Integer versionZero = Integer.valueOf(0); this.setPropertyOverride(test, null, propName, versionZero, propValue); } // Done if (logger.isDebugEnabled()) { logger.debug("Copied test: \n" + " From Test: " + copyOfTest + "\n" + " New Test: " + test); } return true; } /** * Create a new test for the precise release and schema * * @param test * a globally-unique name using * {@link ConfigConstants#TEST_NAME_REGEX} * @param description * any description * @param release * the test definition software release * @param schema * the schema number * @return <tt>true</tt> if the test was written other <tt>false</tt> if not */ public boolean createTest(String test, String description, String release, Integer schema) { if (test == null || test.length() == 0) { throw new IllegalArgumentException("Name length must be non-zero"); } else if (release == null || schema == null) { throw new IllegalArgumentException("A release and schema number must be supplied for a test."); } Pattern pattern = Pattern.compile(TEST_NAME_REGEX); Matcher matcher = pattern.matcher(test); if (!matcher.matches()) { throw new IllegalArgumentException("The test name '" + test + "' is invalid. " + "Test names must start with a character and contain only characters, numbers or underscore."); } // There are no properties to start with DBObject writeObj = BasicDBObjectBuilder.start().add(FIELD_NAME, test) .add(FIELD_VERSION, Integer.valueOf(0)).add(FIELD_DESCRIPTION, description) .add(FIELD_RELEASE, release).add(FIELD_SCHEMA, schema).get(); try { WriteResult result = tests.insert(writeObj); if (logger.isDebugEnabled()) { logger.debug("Created test: " + result + "\n" + " Name: " + test + "\n" + " Descr: " + description + "\n" + " Release: " + release + "\n" + " Schema: " + schema); } return true; } catch (DuplicateKeyException e) { if (logger.isDebugEnabled()) { logger.debug("Test exists: " + test + "."); } return false; } } /** * Update an existing test to use new test details * * @param name * the name of the test (must exist) * @param version * the version of the test for concurrency checking * @param newName * the new test name * @param newDescription * the new description or <tt>null</tt> ot leave it * @param newRelease * the new software release or <tt>null</tt> to leave it * @param newSchema * the new schema number or <tt>null</tt> to leave it * @return <tt>true</tt> if the test run was modified or <tt>false</tt> if * not */ public boolean updateTest(String name, int version, String newName, String newDescription, String newRelease, Integer newSchema) { if (name == null) { throw new IllegalArgumentException("Updated requires a name and version."); } // Find the test by name and version DBObject queryObj = QueryBuilder.start().and(FIELD_NAME).is(name).and(FIELD_VERSION).is(version).get(); // Handle version wrap-around Integer newVersion = version >= Short.MAX_VALUE ? 1 : version + 1; // Gather all the setters required BasicDBObjectBuilder setObjBuilder = BasicDBObjectBuilder.start().add(FIELD_VERSION, newVersion); if (newName != null) { Pattern pattern = Pattern.compile(TEST_NAME_REGEX); Matcher matcher = pattern.matcher(newName); if (!matcher.matches()) { throw new IllegalArgumentException("The test name '" + newName + "' is invalid. " + "Test names must start with a character and contain only characters, numbers or underscore."); } setObjBuilder.add(FIELD_NAME, newName); } if (newDescription != null) { setObjBuilder.add(FIELD_DESCRIPTION, newDescription); } if (newRelease != null) { setObjBuilder.add(FIELD_RELEASE, newRelease); } if (newSchema != null) { setObjBuilder.add(FIELD_SCHEMA, newSchema); } DBObject setObj = setObjBuilder.get(); // Now push the values to set into the update DBObject updateObj = BasicDBObjectBuilder.start().add("$set", setObj).get(); WriteResult result = tests.update(queryObj, updateObj); boolean written = (result.getN() > 0); // Done if (logger.isDebugEnabled()) { if (written) { logger.debug("Updated test: \n" + " Test: " + name + "\n" + " Update: " + updateObj); } else { logger.debug("Did not update test: " + name); } } return written; } /** * Delete an existing test * * @param test * the name of the test (must exist) * @return <tt>true</tt> if the test was deleted or <tt>false</tt> if not */ public boolean deleteTest(String test) { DBObject testObj = getTest(test, false); if (testObj == null) { // The test no longer exists, so the run effectively doesn't either logger.warn("Test not found: " + test); return false; } ObjectId testObjId = (ObjectId) testObj.get(FIELD_ID); // Find the test by name and version DBObject testDelObj = QueryBuilder.start().and(FIELD_ID).is(testObjId).get(); WriteResult result = tests.remove(testDelObj); boolean written = (result.getN() > 0); // Clean up test-related runs DBObject runDelObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).get(); testRuns.remove(runDelObj); // Clean up properties DBObject propDelObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).get(); testProps.remove(propDelObj); // Done if (logger.isDebugEnabled()) { if (written) { logger.debug("Deleted test: " + test); } else { logger.debug("Did not delete test: " + test); } } return written; } /** * Get the test run names associated with a given test * * @param test * the name of the test * @return the names of all test runs associated with the given test */ public List<String> getTestRunNames(String test) { DBObject testObj = getTest(test, false); if (testObj == null) { // The test no longer exists, so the run effectively doesn't either logger.warn("Test not found: " + test); return Collections.emptyList(); } ObjectId testObjId = (ObjectId) testObj.get(FIELD_ID); DBObject queryObj = QueryBuilder.start().and(FIELD_TEST).is(testObjId).get(); DBObject fieldsObj = BasicDBObjectBuilder.start().add(FIELD_ID, true).add(FIELD_NAME, true).get(); DBCursor cursor = testRuns.find(queryObj, fieldsObj); List<String> testRunNames = new ArrayList<String>(cursor.count()); try { while (cursor.hasNext()) { DBObject testRunObj = cursor.next(); String testRunName = (String) testRunObj.get(FIELD_NAME); testRunNames.add(testRunName); } } finally { cursor.close(); } // Done if (logger.isDebugEnabled()) { logger.debug("Found and returned " + testRunNames.size() + " test run names for test '" + test + "'"); } return testRunNames; } /** * @param test * only fetch runs for this test or <tt>null</tt> to get all test * runs * @param testRunStates * optional states that the test runs must be in or empty for all * @return a cursor onto the test runs for the given test */ public DBCursor getTestRuns(String test, int skip, int count, TestRunState... testRunStates) { BasicDBObjectBuilder queryObjBuilder = BasicDBObjectBuilder.start(); if (test != null) { ObjectId testObjId = getTestId(test); if (testObjId == null) { // The test no longer exists, so the run effectively doesn't // either logger.warn("Test not found: " + test); // Use a ficticious ID that will never match testObjId = new ObjectId(); } queryObjBuilder.add(FIELD_TEST, testObjId); } // Build query for the test run states if (testRunStates.length > 0) { List<String> stateStrs = new ArrayList<String>(testRunStates.length); for (int i = 0; i < testRunStates.length; i++) { stateStrs.add(testRunStates[i].toString()); } queryObjBuilder.push(FIELD_STATE); queryObjBuilder.add("$in", stateStrs); } DBObject queryObj = queryObjBuilder.get(); DBObject fieldsObj = BasicDBObjectBuilder.start().add(FIELD_NAME, true).add(FIELD_TEST, true) .add(FIELD_VERSION, true).add(FIELD_DESCRIPTION, true).add(FIELD_STATE, true) .add(FIELD_SCHEDULED, true).add(FIELD_STARTED, true).add(FIELD_STOPPED, true) .add(FIELD_COMPLETED, true).add(FIELD_DURATION, true).add(FIELD_PROGRESS, true) .add(FIELD_RESULTS_SUCCESS, true).add(FIELD_RESULTS_FAIL, true).add(FIELD_RESULTS_TOTAL, true) .add(FIELD_SUCCESS_RATE, true).get(); DBCursor dbCursor = testRuns.find(queryObj, fieldsObj).skip(skip).limit(count); // Done if (logger.isDebugEnabled()) { logger.debug( "Fetched test runs: \n" + " Test: " + test + "\n" + " Results: " + dbCursor.count()); } return dbCursor; } /** * Fetch the low-level ID for a test run */ private ObjectId getTestRunId(ObjectId testObjId, String run) { DBObject queryObj = QueryBuilder.start().and(FIELD_TEST).is(testObjId).and(FIELD_NAME).is(run).get(); DBObject fieldsObj = BasicDBObjectBuilder.start().add(FIELD_ID, true).get(); DBObject runObj = testRuns.findOne(queryObj, fieldsObj); ObjectId runObjId = null; if (runObj != null) { runObjId = (ObjectId) runObj.get(FIELD_ID); } // Done if (logger.isTraceEnabled()) { logger.trace("Fetched test run ID: \n" + " Test ID: " + testObjId + "\n" + " Run: " + run + "\n" + " Result: " + runObjId); } return runObjId; } /** * Retrieve the data for given test run * * @param runObjId * (ObjectId, mandatory) the ID of the test run * * @param includeProperties * <tt>true</tt> to flesh out all the properties * * @return the test object */ public DBObject getTestRun(ObjectId runObjId, boolean includeProperties) throws ObjectNotFoundException { ArgumentCheck.checkMandatoryObject(runObjId, "runObjId"); DBObject queryObj = QueryBuilder.start().and(FIELD_ID).is(runObjId).get(); BasicDBObjectBuilder fieldsObjBuilder = BasicDBObjectBuilder.start().add(FIELD_NAME, true) .add(FIELD_TEST, true).add(FIELD_VERSION, true).add(FIELD_DESCRIPTION, true).add(FIELD_STATE, true) .add(FIELD_SCHEDULED, true).add(FIELD_STARTED, true).add(FIELD_STOPPED, true) .add(FIELD_COMPLETED, true).add(FIELD_DURATION, true).add(FIELD_PROGRESS, true) .add(FIELD_RESULTS_SUCCESS, true).add(FIELD_RESULTS_FAIL, true).add(FIELD_RESULTS_TOTAL, true) .add(FIELD_SUCCESS_RATE, true).add(FIELD_DRIVERS, true); DBObject fieldsObj = fieldsObjBuilder.get(); DBObject runObj = testRuns.findOne(queryObj, fieldsObj); if (runObj == null) { // The test run no longer exists throw new ObjectNotFoundException("Test run"); } if (includeProperties) { ObjectId testObjId = (ObjectId) runObj.get(FIELD_TEST); String testName = runObj.get(FIELD_TEST).toString(); String runName = runObj.get(FIELD_NAME).toString(); BasicDBList propsList = getTestRunProperties(testObjId, runObjId, testName, runName); runObj.put(FIELD_PROPERTIES, propsList); } // Done if (logger.isDebugEnabled()) { logger.debug("Found test run " + runObjId + ": " + runObj); } return runObj; } /** * Checks whether test and test run still exists and cleans up the database * if not * * @param testName * (String, mandatory) test name * @param testRunName * (String, optional) test run name * * @since 2.1.2 */ private boolean cleanup(String testName, String testRunName) { ArgumentCheck.checkMandatoryString(testName, "testName"); boolean written = false; // first check for the test itself ... ObjectId testObjId = getTestId(testName); if (testObjId == null) { String msg = "Test run no longer has a matching test definition: \n" + " Test: " + testName; if (null != testRunName && !testRunName.isEmpty()) { msg += "\n" + " Run: " + testRunName; } logger.warn(msg); written = this.deleteTest(testName); } return written; } /** * Returns the "final" test run properties of the test run. * * @param testObjId * (ObjectId, optional) * @param runObjId * (ObjectId, optional) * @param testName * (String, mandatory) * @param testRunName * (String, mandatory) * * @return final property collection with overrides of the test/run or * exception * * @throws ObjectNotFoundException */ private BasicDBList getTestRunProperties(ObjectId testObjId, ObjectId runObjId, String testName, String testRunName) throws ObjectNotFoundException { // get map of properties Map<String, DBObject> propsMap = getTestRunPropertiesMap(testObjId, runObjId, testName, testRunName); // Turn into a list and return return MongoTestDAO.getPropertyList(propsMap); } /** * Returns the map with "final" test run properties of the test run. * * @param testObjId * (ObjectId, optional) * @param runObjId * (ObjectId, optional) * @param testName * (String, mandatory) * @param testRunName * (String, mandatory) * * @return (Map<String, DBObject>) or exception * * @throws ObjectNotFoundException */ public Map<String, DBObject> getTestRunPropertiesMap(ObjectId testObjId, ObjectId runObjId, String testName, String testRunName) throws ObjectNotFoundException { ArgumentCheck.checkMandatoryString(testName, "testName"); ArgumentCheck.checkMandatoryString(testRunName, "testRunName"); // check optional arguments if (null == testObjId) { testObjId = getTestId(testName); if (null == testObjId) { cleanup(testName, testRunName); throw new ObjectNotFoundException(testName); } } if (null == runObjId) { runObjId = getTestRunId(testObjId, testRunName); if (null == runObjId) { throw new ObjectNotFoundException(testName + "." + testRunName); } } // Retrieve the test DBObject testObj = getTest(testObjId, false); if (testObj == null) { cleanup(testName, testRunName); throw new ObjectNotFoundException(testName + "." + testRunName); } // Get the associated test definition String release = (String) testObj.get(FIELD_RELEASE); Integer schema = (Integer) testObj.get(FIELD_SCHEMA); TestDefEntry testDefEntry = getTestDefCached(release, schema); if (testDefEntry == null) { cleanup(testName, testRunName); throw new ObjectNotFoundException(testName + "." + testRunName); } // now get the properties // Start with the properties from the test definition Map<String, DBObject> propsMap = new HashMap<String, DBObject>(testDefEntry.testDefPropsMap); // Fetch the properties for the test DBCursor testPropsCursor = getTestPropertiesRaw(testObjId, null); // Combine MongoTestDAO.mergeProperties(propsMap, testPropsCursor); // Fetch the properties for the test run DBCursor runPropsCursor = getTestPropertiesRaw(testObjId, runObjId); // Combine MongoTestDAO.mergeProperties(propsMap, runPropsCursor); return propsMap; } /** * Retrieve the data for given test run * * @param test * (String, mandatory) the name of the test * @param run * (String, mandatory) the test run name * * @param includeProperties * <tt>true</tt> to flesh out all the properties * * @return (DBObject) the test object * * @throws ObjectNotFoundException */ public DBObject getTestRun(String test, String run, boolean includeProperties) throws ObjectNotFoundException { DBObject testObj = getTest(test, false); if (testObj == null) { // The test no longer exists, so the run effectively doesn't either throw new ObjectNotFoundException(test); } ObjectId testObjId = (ObjectId) testObj.get(FIELD_ID); // Get the ID of the test run ObjectId runObjId = getTestRunId(testObjId, run); if (runObjId == null) { // The test run no longer exists throw new ObjectNotFoundException(test + "." + run); } return getTestRun(runObjId, includeProperties); } /** * Create a new test run by copying an existing test run. * <p/> * All property overrides will be copied, which is where the value really * lies. * * @param test * the name of the test to which the run belongs * @param run * a test-unique name using {@link ConfigConstants#RUN_NAME_REGEX} * @param copyOfRun * the test run name to copy * @param copyOfVersion * the version of the test run to copy * @return <tt>true</tt> if the test run was copied or <tt>false</tt> if not */ public boolean copyTestRun(String test, String run, String copyOfRun, int copyOfVersion) { // Get the test DBObject testObj = getTest(test, false); if (testObj == null) { // The test no longer exists, so the run effectively doesn't either logger.warn("Test not found: " + test); return false; } // Get the test run DBObject copyOfTestRunObj; try { copyOfTestRunObj = getTestRun(test, copyOfRun, true); } catch (ObjectNotFoundException e) { copyOfTestRunObj = null; } if (copyOfTestRunObj == null || !Integer.valueOf(copyOfVersion).equals(copyOfTestRunObj.get(FIELD_VERSION))) { logger.warn("Did not find test run to copy: " + test + "." + copyOfRun + " (V" + copyOfVersion + ")"); return false; } String description = (String) copyOfTestRunObj.get(FIELD_DESCRIPTION); // Copy the test run if (!createTestRun(test, run, description)) { logger.warn("Failed to create a test run via copy: " + test + "." + run); return false; } // Get the properties to copy BasicDBList copyOfPropObjs = (BasicDBList) copyOfTestRunObj.get(FIELD_PROPERTIES); if (copyOfPropObjs == null) { copyOfPropObjs = new BasicDBList(); } for (Object obj : copyOfPropObjs) { DBObject copyPropObj = (DBObject) obj; Integer copyPropVer = (Integer) copyPropObj.get(FIELD_VERSION); if (copyPropVer == null || copyPropVer.intValue() <= 0) { // There has been no override continue; } String copyPropOrigin = (String) copyPropObj.get(FIELD_ORIGIN); if (!TestPropertyOrigin.RUN.name().equals(copyPropOrigin)) { // We also don't copy values that did not originate in the test // run continue; } String propName = (String) copyPropObj.get(FIELD_NAME); String propValue = (String) copyPropObj.get(FIELD_VALUE); Integer versionZero = Integer.valueOf(0); this.setPropertyOverride(test, run, propName, versionZero, propValue); } // Done if (logger.isDebugEnabled()) { logger.debug("Copied test run: \n" + " Test: " + test + "\n" + " From Run: " + copyOfRun + "\n" + " New Run: " + run); } return true; } /** * Create a new test run * * @param test * the name of the test to which the run belongs * @param run * a test-unique name using {@link ConfigConstants#RUN_NAME_REGEX} * @param description * any description * @return <tt>true</tt> if the test run was written other <tt>false</tt> if * not */ public boolean createTestRun(String test, String run, String description) { if (run == null || run.length() == 0) { throw new IllegalArgumentException("Name length must be non-zero"); } Pattern pattern = Pattern.compile(RUN_NAME_REGEX); Matcher matcher = pattern.matcher(run); if (!matcher.matches()) { throw new IllegalArgumentException("The test run name '" + run + "' is invalid. " + "Test run names may contain only characters, numbers or underscore."); } DBObject testObj = getTest(test, false); if (testObj == null) { // The test no longer exists, so the run effectively doesn't either logger.warn("Test not found: " + test); return false; } ObjectId testObjId = (ObjectId) testObj.get(FIELD_ID); // There are no properties to start with DBObject writeObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).add(FIELD_NAME, run) .add(FIELD_VERSION, Integer.valueOf(0)).add(FIELD_DESCRIPTION, description) .add(FIELD_STATE, TestRunState.NOT_SCHEDULED.toString()).add(FIELD_SCHEDULED, Long.valueOf(-1L)) .add(FIELD_STARTED, Long.valueOf(-1L)).add(FIELD_STOPPED, Long.valueOf(-1L)) .add(FIELD_COMPLETED, Long.valueOf(-1L)).add(FIELD_DURATION, Long.valueOf(0L)) .add(FIELD_PROGRESS, Double.valueOf(0.0D)).add(FIELD_RESULTS_SUCCESS, Long.valueOf(0L)) .add(FIELD_RESULTS_FAIL, Long.valueOf(0L)).add(FIELD_RESULTS_TOTAL, Long.valueOf(0L)) .add(FIELD_SUCCESS_RATE, Double.valueOf(1.0)).add(FIELD_DRIVERS, new BasicDBList()) // Ensure we // have an // empty // list to // start .get(); try { WriteResult result = testRuns.insert(writeObj); if (logger.isDebugEnabled()) { logger.debug("Created test run: " + result + "\n" + " Test: " + test + "\n" + " Name: " + run + "\n" + " Descr: " + description); } return true; } catch (DuplicateKeyException e) { if (logger.isDebugEnabled()) { logger.debug("Test run exists: " + test + ". " + run); } return false; } } /** * Update an existing test run with new details * * @param test * the name of the test * @param run * the name of the test run (must exist) * @param version * the version of the test for concurrency checking * @param newName * the new name of the test run * @param newDescription * the new description or <tt>null</tt> ot leave it * @return <tt>true</tt> if the test run was modified or <tt>false</tt> if * not */ public boolean updateTestRun(String test, String run, int version, String newName, String newDescription) { if (test == null || run == null) { throw new IllegalArgumentException("Updated requires a name and version."); } // Get the test DBObject testObj = getTest(test, false); if (testObj == null) { if (logger.isDebugEnabled()) { logger.debug("Unable to update test run; test not found: " + test); } return false; } // Find the test run by name and version DBObject queryObj = QueryBuilder.start().and(FIELD_TEST).is(testObj.get(FIELD_ID)).and(FIELD_NAME).is(run) .and(FIELD_VERSION).is(version).get(); // Handle version wrap-around Integer newVersion = version >= Short.MAX_VALUE ? 1 : version + 1; // Gather all the setters required BasicDBObjectBuilder setObjBuilder = BasicDBObjectBuilder.start().add(FIELD_VERSION, newVersion); if (newName != null) { Pattern pattern = Pattern.compile(RUN_NAME_REGEX); Matcher matcher = pattern.matcher(newName); if (!matcher.matches()) { throw new IllegalArgumentException("The test run name '" + newName + "' is invalid. " + "Test run names may only contain characters, numbers or underscore."); } setObjBuilder.add(FIELD_NAME, newName); } if (newDescription != null) { setObjBuilder.add(FIELD_DESCRIPTION, newDescription); } DBObject setObj = setObjBuilder.get(); // Now push the values to set into the update DBObject updateObj = BasicDBObjectBuilder.start().add("$set", setObj).get(); WriteResult result = testRuns.update(queryObj, updateObj); boolean written = (result.getN() > 0); // Done if (logger.isDebugEnabled()) { if (written) { logger.debug("Updated test run: \n" + " Test: " + test + "\n" + " Run: " + run + "\n" + " Update: " + updateObj); } else { logger.debug("Did not update test run: " + test + "." + run); } } return written; } /** * Update the run state of a test run. * <p/> * The test run {@link TestConstants#FIELD_STATE state} will be set based on * the values. * <p/> * Note that a test run can either be stopped or completed but not both. In * both cases, * though, the test run must have been scheduled and then started. * * @param test * the name of the test * @param run * the name of the test run (must exist) * @param version * the version of the test for concurrency checking * @param testRunState * the test run state to set (<null> to ignore) * @param scheduled * the time when the test run is scheduled to start (<null> to * ignore) * @param started * the time when the test run started (<null> to ignore) * @param stopped * the time when the test run was stopped (<null> to ignore) * @param completed * the time when the test run was completed (<null> to ignore) * @param duration * the time the test has been running for in ms (<null> to ignore) * @param progress * the new progress for the test run (<null> to ignore) * @param resultsSuccess * the number of successful results (<null> to ignore) * @param resultsFailure * the number of failed results (<null> to ignore) * @return <tt>true</tt> if the test run was modified or <tt>false</tt> if * not */ public boolean updateTestRunState(ObjectId runId, int version, TestRunState testRunState, Long scheduled, Long started, Long stopped, Long completed, Long duration, Double progress, Long resultsSuccess, Long resultsFail) { // Find the test run by name and version DBObject queryObj = QueryBuilder.start().and(FIELD_ID).is(runId).and(FIELD_VERSION).is(version).get(); // Gather all the setters required BasicDBObjectBuilder setObjBuilder = BasicDBObjectBuilder.start(); if (testRunState != null) { setObjBuilder.add(FIELD_STATE, testRunState.toString()); } if (scheduled != null) { setObjBuilder.add(FIELD_SCHEDULED, scheduled); } if (started != null) { setObjBuilder.add(FIELD_STARTED, started); } if (stopped != null) { setObjBuilder.add(FIELD_STOPPED, stopped); } if (completed != null) { setObjBuilder.add(FIELD_COMPLETED, completed); } if (duration != null) { setObjBuilder.add(FIELD_DURATION, duration); } if (progress != null) { // Adjust accuracy of the progress long progressLong = Math.round(progress * 10000.0); if (progressLong < 0L || progressLong > 10000L) { throw new IllegalArgumentException("Progress must be expressed as a double in range [0.0, 1.0]."); } progress = progressLong / 10000.0; // Accuracy setObjBuilder.add(FIELD_PROGRESS, progress); } if (resultsSuccess != null || resultsFail != null) { if (resultsSuccess == null || resultsFail == null) { throw new IllegalArgumentException("resultsSuccess and resultsFail must be updated together."); } long resultsTotal = Long.valueOf(resultsSuccess.longValue() + resultsFail.longValue()); double successRate = (resultsTotal == 0) ? 1.0 : (resultsSuccess / (double) resultsTotal); setObjBuilder.add(FIELD_RESULTS_SUCCESS, resultsSuccess); setObjBuilder.add(FIELD_RESULTS_FAIL, resultsFail); setObjBuilder.add(FIELD_RESULTS_TOTAL, resultsTotal); setObjBuilder.add(FIELD_SUCCESS_RATE, successRate); } if (resultsFail != null) { setObjBuilder.add(FIELD_RESULTS_FAIL, resultsFail); } // Check that we are actually going to do something if (setObjBuilder.get().keySet().size() == 0) { if (logger.isDebugEnabled()) { logger.debug("No updates provided for test run: " + runId); } return false; } // Handle version wrap-around Integer newVersion = version >= Short.MAX_VALUE ? 1 : version + 1; setObjBuilder.add(FIELD_VERSION, newVersion); // Get the object containing the set values DBObject setObj = setObjBuilder.get(); // Now push the values to set into the update DBObject updateObj = BasicDBObjectBuilder.start().add("$set", setObj).get(); WriteResult result = testRuns.update(queryObj, updateObj); boolean written = (result.getN() > 0); // Done if (logger.isDebugEnabled()) { if (written) { logger.debug("Updated test run state: \n" + " Run ID: " + runId + "\n" + " Update: " + updateObj); } else { logger.debug("Did not update test run state: " + runId); } } return written; } /** * Register a driver with a test run * * @param runObjId * the ID of the test run * @param driverId * the ID of the driver to include */ public void addTestRunDriver(ObjectId runObjId, String driverId) { // Find the test run DBObject queryObj = QueryBuilder.start().and(FIELD_ID).is(runObjId).get(); DBObject updateObj = BasicDBObjectBuilder.start().push("$addToSet").add(FIELD_DRIVERS, driverId).pop() .get(); DBObject runObj = testRuns.findAndModify(queryObj, null, null, false, updateObj, true, false); // Done if (logger.isDebugEnabled()) { logger.debug("Added driver ID to run drivers: \n" + " Run ID: " + runObjId + "\n" + " Driver: " + driverId + "\n" + " Drivers: " + runObj.get(FIELD_DRIVERS)); } } /** * Derigister a driver from a test run * * @param runObjId * the ID of the test run * @param driverId * the ID of the driver to remove */ public void removeTestRunDriver(ObjectId runObjId, String driverId) { // Find the test run DBObject queryObj = QueryBuilder.start().and(FIELD_ID).is(runObjId).get(); DBObject updateObj = BasicDBObjectBuilder.start().push("$pull").add(FIELD_DRIVERS, driverId).pop().get(); DBObject runObj = testRuns.findAndModify(queryObj, null, null, false, updateObj, true, false); // Done if (logger.isDebugEnabled()) { logger.debug("Removed driver ID from run drivers: \n" + " Run ID: " + runObjId + "\n" + " Driver: " + driverId + "\n" + " Drivers: " + runObj.get(FIELD_DRIVERS)); } } /** * Delete an existing test run * * @param runObjId * the ID of the test run * @return <tt>true</tt> if the test run was deleted or <tt>false</tt> if * not */ public boolean deleteTestRun(ObjectId runObjId) { // Get the test run DBObject runObj; try { runObj = getTestRun(runObjId, false); } catch (ObjectNotFoundException e) { logger.warn("Unable to delete test run as it does not exist: " + runObjId, e); return false; } ObjectId testObjId = (ObjectId) runObj.get(FIELD_TEST); // Find the test run DBObject queryObj = QueryBuilder.start().and(FIELD_ID).is(runObjId).get(); WriteResult result = testRuns.remove(queryObj); boolean written = (result.getN() > 0); // Clean up properties DBObject propDelObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).add(FIELD_RUN, runObjId) .get(); testProps.remove(propDelObj); // Done if (logger.isDebugEnabled()) { if (written) { logger.debug("Deleted test run: " + queryObj); } else { logger.debug("Did not delete test run: " + runObjId); } } return written; } /** * Delete an existing test run * * @param test * the name of the test * @param run * the run name (must exist) * @return <tt>true</tt> if the test run was deleted or <tt>false</tt> if * not */ public boolean deleteTestRun(String test, String run) { // Get the test ID ObjectId testObjId = getTestId(test); if (testObjId == null) { logger.warn("Unable to delete test run; test not found: " + test); return false; } // Get the run ID ObjectId runObjId = getTestRunId(testObjId, run); if (runObjId == null) { logger.warn("Unable to delete test run; run not found: " + test + "." + run); return false; } // Delete by ID boolean deleted = deleteTestRun(runObjId); // Done if (logger.isDebugEnabled()) { if (deleted) { logger.debug("Deleted test run: " + test + "." + run); } else { logger.debug("Did not delete test run: " + test + "." + run); } } return deleted; } /** * Utility method to copy a DBObject */ private static DBObject copyDBObject(DBObject input) { // Copy the property to a new instance BasicDBObjectBuilder newPropObjBuilder = BasicDBObjectBuilder.start(); for (String fieldName : input.keySet()) { Object fieldValue = input.get(fieldName); newPropObjBuilder.add(fieldName, fieldValue); } return newPropObjBuilder.get(); } /** * Merges in overriding values from a collection of properties into a map * * @param propsMap * the properties to update; the map is updated in place * @param overridingPropObjs * the properties that taken precedence */ private static void mergeProperties(Map<String, DBObject> propsMap, DBCursor overridingPropObjs) { // Keep track of properties that were not overridden Set<String> unmerged = new HashSet<String>(propsMap.keySet()); while (overridingPropObjs != null && overridingPropObjs.hasNext()) { DBObject overridePropObj = overridingPropObjs.next(); String key = (String) overridePropObj.get(FIELD_NAME); unmerged.remove(key); MongoTestDAO.mergeProperty(propsMap, overridePropObj); } // All the untouched properties need to have 'value' moved to 'default' for (String key : unmerged) { DBObject sourceObj = propsMap.get(key); String sourceObjValue = (String) sourceObj.get(FIELD_VALUE); if (sourceObjValue == null) { // There is no value. So the default remains the same. continue; } // Copy the object DBObject targetObj = copyDBObject(sourceObj); // Set the new default targetObj.put(FIELD_DEFAULT, sourceObjValue); // Remove the value targetObj.removeField(FIELD_VALUE); // Reset the version number targetObj.put(FIELD_VERSION, VERSION_ZERO); // Replace the object propsMap.put(key, targetObj); } // Done } /** * Merges in overriding value from an object into a map * * @param propsMap * the properties to update; the map is updated in place * @param overridePropObj * the property that taken precedence */ private static void mergeProperty(Map<String, DBObject> propsMap, DBObject overridePropObj) { String key = (String) overridePropObj.get(FIELD_NAME); Integer overrideVersion = (Integer) overridePropObj.get(FIELD_VERSION); String overrideValue = (String) overridePropObj.get(FIELD_VALUE); String overrideOrigin = (String) overridePropObj.get(FIELD_ORIGIN); DBObject propObj = propsMap.get(key); if (propObj == null) { // The property is not (or is no longer) relevant to the test return; } // Copy the property to a new instance DBObject newPropObj = copyDBObject(propObj); // If the new property already has a value, then that becomes the // default String newPropValue = (String) newPropObj.get(FIELD_VALUE); if (newPropValue != null) { newPropObj.put(FIELD_DEFAULT, newPropValue); newPropObj.removeField(FIELD_VALUE); } // Now overwrite with the overriding values newPropObj.put(FIELD_VERSION, overrideVersion); newPropObj.put(FIELD_ORIGIN, overrideOrigin); newPropObj.put(FIELD_VALUE, overrideValue); // Put that into the map propsMap.put(key, newPropObj); // Done } /** * Convert the map entries into a collection of DBObjects */ private static BasicDBList getPropertyList(Map<String, DBObject> propsMap) { BasicDBList list = new BasicDBList(); for (DBObject propObj : propsMap.values()) { list.add(propObj); } return list; } /** * Get all test-specific overrides for properties * * @param testObjId * the ID of the test * @param runObjId * the ID of the test run or <tt>null</tt> to find generic test * properties * @return all properties for the test or test run */ private DBCursor getTestPropertiesRaw(ObjectId testObjId, ObjectId runObjId) { DBObject queryObj = QueryBuilder.start().and(FIELD_TEST).is(testObjId).and(FIELD_RUN).is(runObjId).get(); return testProps.find(queryObj); } /** * Get all test-specific overrides for properties. Note that this does not * include any * inherited fields from the property definitions. * * @param testObjId * the ID of the test * @param runObjId * the ID of the test run or <tt>null</tt> to find generic test * properties * @param propertyName * the name of the property (never <tt>null</tt>) * @return the property for the test run as a cursor */ private DBCursor getTestPropertyRaw(ObjectId testObjId, ObjectId runObjId, String propertyName) { DBObject queryObj = QueryBuilder.start().and(FIELD_TEST).is(testObjId).and(FIELD_RUN).is(runObjId) .and(FIELD_NAME).is(propertyName).get(); return testProps.find(queryObj); } /** * Retrieve the effective property for a given test, test run and property * name. * <p/> * This combines all values in the property inheritance hierarchy to get to * the value * applicable to the test or test run. * * @param test * the name of the test * @param run * the name of the run or <tt>null</tt> to find the value for the * test only * @param propertyName * the name of the property (never <tt>null</tt>) * @return the property for the test or <tt>null</tt> if it does not exist */ public DBObject getProperty(String test, String run, String propertyName) { if (propertyName == null) { throw new IllegalArgumentException("'propertyName' may not be null."); } // First see if there is a test as requested DBObject testObj = getTest(test, false); if (testObj == null) { // The test does not exist if (logger.isDebugEnabled()) { logger.debug("Could not get property for test that does not exist: \n" + " Test: " + test + "\n" + " Run: " + run + "\n" + " Property: " + propertyName); } return null; } ObjectId testObjId = (ObjectId) testObj.get(FIELD_ID); // Get the property definition String release = (String) testObj.get(FIELD_RELEASE); Integer schema = (Integer) testObj.get(FIELD_SCHEMA); TestDefEntry testDefEntry = getTestDefCached(release, schema); if (testDefEntry == null) { logger.warn("Test definition not found: " + testObj); return null; } Map<String, DBObject> propsMap = new HashMap<String, DBObject>(testDefEntry.testDefPropsMap); // Get the overriding value from the test, if present DBCursor testPropsCursor = getTestPropertyRaw(testObjId, null, propertyName); // Combine MongoTestDAO.mergeProperties(propsMap, testPropsCursor); // Check if we want the next level of overrides if (run != null) { ObjectId runObjId = getTestRunId(testObjId, run); if (runObjId == null) { // The test no longer exists, so the run effectively doesn't // either logger.warn("Test run not found: " + test + "." + run); return null; } // Get the overriding value from the test, if present DBCursor runPropsCursor = getTestPropertyRaw(testObjId, runObjId, propertyName); // Combine MongoTestDAO.mergeProperties(propsMap, runPropsCursor); } // Pull out the property we want DBObject propObj = propsMap.get(propertyName); // Done if (logger.isDebugEnabled()) { String msg = (propObj == null) ? "Property not found: \n" : "Found property: \n"; logger.debug(msg + " Test: " + test + "\n" + " Run: " + run + "\n" + " Property: " + propObj); } return propObj; } /** * Override a specific test property value. * <p/> * A version number of zero indicates that there is no existing override * defined.<br/> * A value of <tt>null</tt> indicates that the existing override should be * removed. * * @param test * the name of the test * @param run * the name of the test run (<tt>null</tt> to reference the test * alone) * @param propertyName * the name of the property * @param version * the current version of the property * @param value * the new value to set or <tt>null</tt> to remove any override * @throws IllegalStateException * if the test has started */ public boolean setPropertyOverride(String test, String run, String propertyName, int version, String value) { // Handle version wrap-around int newVersion = (version >= Short.MAX_VALUE) ? 1 : version + 1; // We need to keep the IDs ObjectId runObjId = null; ObjectId testObjId = null; String origin = null; if (run == null) { origin = TestPropertyOrigin.TEST.name(); // Get the test DBObject testObj = getTest(test, false); if (testObj == null) { logger.warn("Unable to set property override for test as it was not found: " + test); return false; } // Get the ID testObjId = (ObjectId) testObj.get(FIELD_ID); } else { origin = TestPropertyOrigin.RUN.name(); // Get the test run DBObject runObj; try { runObj = getTestRun(test, run, false); } catch (ObjectNotFoundException e1) { logger.warn("Test run not found: " + test + "." + run, e1); return false; } // Check the state of the run try { TestRunState runState = TestRunState.valueOf((String) runObj.get(FIELD_STATE)); if (runState != TestRunState.NOT_SCHEDULED && runState != TestRunState.SCHEDULED) { throw new IllegalStateException( "Property overrides can only be set for test runs that have not started: \n" + " Run: " + runObj + "\n" + " Property: " + propertyName); } } catch (IllegalArgumentException e) { logger.error("Test run state is unknown: " + runObj); this.deleteTestRun(runObjId); return false; } // Get the ID runObjId = (ObjectId) runObj.get(FIELD_ID); testObjId = (ObjectId) runObj.get(FIELD_TEST); } DBObject queryObj = QueryBuilder.start().and(FIELD_TEST).is(testObjId).and(FIELD_RUN).is(runObjId) .and(FIELD_NAME).is(propertyName).and(FIELD_VERSION).is(Integer.valueOf(version)).get(); DBObject updateObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).add(FIELD_RUN, runObjId) .add(FIELD_NAME, propertyName).add(FIELD_VERSION, Integer.valueOf(newVersion)) .add(FIELD_VALUE, value).add(FIELD_ORIGIN, origin).get(); WriteResult result = null; boolean written = false; try { if (value == null) { // remove property result = testProps.remove(queryObj); written = (result.getN() > 0); } else { // A value was provided, so either INSERT or UPDATE if (version == 0) { // This indicates that no override should exist, yet result = testProps.insert(updateObj); written = true; } else { // There must an update result = testProps.update(queryObj, updateObj); written = result.getN() > 0; } } } catch (DuplicateKeyException e) { written = false; } // Done if (logger.isDebugEnabled()) { if (written) { logger.debug("Wrote property override: \n" + " Test: " + test + "\n" + " Run: " + run + "\n" + " Property: " + propertyName + "\n" + " Version: " + version); } else { logger.debug( "Did not update property override: \n" + " Test: " + test + "\n" + " Run: " + run + "\n" + " Property: " + propertyName + "\n" + " Version: " + version); } } return written; } /** * Record a final set of locked properties for a test run. The properties * written will not be updatable. * * @param testObjId * the ID of the test * @param runObjId * the ID of the test run * * @throws ObjectNotFoundException */ public void lockProperties(ObjectId testObjId, ObjectId runObjId) throws ObjectNotFoundException { // Get all the test run overrides DBObject runObj = getTestRun(runObjId, true); BasicDBList propObjs = (BasicDBList) runObj.get(FIELD_PROPERTIES); // First remove all current property overrides for the run DBObject propDelObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).add(FIELD_RUN, runObjId) .get(); testProps.remove(propDelObj); // Now add them all back int version = Short.MAX_VALUE + 1; // This puts it out of // the normal range of // editing List<DBObject> insertObjList = new ArrayList<DBObject>(propObjs.size()); for (Object obj : propObjs) { DBObject propObj = (DBObject) obj; String propName = (String) propObj.get(FIELD_NAME); String propOrigin = (String) propObj.get(FIELD_ORIGIN); Object defValue = propObj.get(FIELD_DEFAULT); Object value = propObj.get(FIELD_VALUE); if (value == null) { // We override ALL values value = defValue; } DBObject insertObj = BasicDBObjectBuilder.start().add(FIELD_TEST, testObjId).add(FIELD_RUN, runObjId) .add(FIELD_NAME, propName).add(FIELD_VERSION, Integer.valueOf(version)) .add(FIELD_ORIGIN, propOrigin).add(FIELD_VALUE, value).get(); insertObjList.add(insertObj); } // Do the bulk insert try { testProps.insert(insertObjList, WriteConcern.SAFE); } catch (MongoException e) { StringBuilder sb = new StringBuilder(); sb.append("Lost test run property overrides: \n" + " Error: " + e.getMessage() + "\n" + " Props:"); for (Object propObj : propObjs) { sb.append("\n").append(" ").append(propObj); } String msg = sb.toString(); logger.error(msg); throw new RuntimeException(msg, e); } // Done if (logger.isDebugEnabled()) { logger.debug("Successfully fixed property overrides for run: " + runObjId); } } /** * Fetch masked property names (passwords) by release and version name. * * @param release * (String, mandatory) test release name * @param version * (Integer) test version number * * @return (Set<String>) or exception * * @throws ObjectNotFoundException * @since 2.1.2 */ public Set<String> getMaskedProperyNames(String release, Integer schema) throws ObjectNotFoundException { ArgumentCheck.checkMandatoryString(release, "release"); Set<String> result = new HashSet<String>(); TestDefEntry testDefEntry = getTestDefCached(release, schema); ObjectNotFoundException.checkObject(testDefEntry, release + "-schema:" + schema); // Start with the properties from the test definition Map<String, DBObject> propsMap = new HashMap<String, DBObject>(testDefEntry.testDefPropsMap); for (final DBObject dbObjProp : propsMap.values()) { if (isMaskedProperty(dbObjProp)) { result.add((String) dbObjProp.get(FIELD_NAME)); } } return result; } /** * Checks if the DBObject contains a field FIELD_MASK and returns true if * set * * @param property * (DBObject) property * * @return true if masked, false if not or field not contained. */ public boolean isMaskedProperty(DBObject property) { ArgumentCheck.checkMandatoryObject(property, "property"); boolean result = false; if (property.containsField(FIELD_MASK)) { Object maskObj = property.get(FIELD_MASK); if (null != maskObj) { if (maskObj instanceof String) { result = ((String) maskObj).equals("true"); } else if (maskObj instanceof Boolean) { result = (Boolean) maskObj; } else { throw new IllegalArgumentException("Unknown type of field '" + FIELD_MASK + "'"); } } } return result; } /** * Fetch masked property names (passwords) by test name. * * @param testName * (String, mandatory) test name * * @return (Set<String>) or exception * * @throws ObjectNotFoundException * @since 2.1.2 */ public Set<String> getMaskedProperyNames(String testName) throws ObjectNotFoundException { ArgumentCheck.checkMandatoryString(testName, "testName"); DBObject queryObj = QueryBuilder.start().and(FIELD_NAME).is(testName).get(); BasicDBObjectBuilder fieldsObjBuilder = BasicDBObjectBuilder.start(FIELD_RELEASE, true).add(FIELD_SCHEMA, true); DBObject testObj = tests.findOne(queryObj, fieldsObjBuilder.get()); ObjectNotFoundException.checkObject(testObj, testName); return getMaskedProperyNames((String) testObj.get(FIELD_RELEASE), (Integer) testObj.get(FIELD_SCHEMA)); } public DBObject importTestRun(String testName, String runName, DBObject importObj) { // create return object DBObject resultObj = new BasicDBObject(); String message = "Import succeeded."; ImportResult result = ImportResult.OK; try { ArgumentCheck.checkMandatoryString(testName, "testName"); ArgumentCheck.checkMandatoryString(runName, "runName"); ArgumentCheck.checkMandatoryObject(importObj, "importObj"); // get object IDs ObjectId testObjId = getTestId(testName); ObjectId runObjId = getTestRunId(testObjId, runName); if (null == testObjId) { throw new ObjectNotFoundException(testName + "." + runName); } // get test definition DBObject queryObj = QueryBuilder.start(FIELD_ID).is(testObjId).get(); BasicDBObjectBuilder fieldsObjBuilder = BasicDBObjectBuilder.start(FIELD_NAME, 1) .add(FIELD_RELEASE, true).add(FIELD_SCHEMA, true); DBObject fieldsObj = fieldsObjBuilder.get(); DBObject testObj = tests.findOne(queryObj, fieldsObj); if (testObj == null) { throw new ObjectNotFoundException(testName + "." + runName); } // get values from test String release = (String) testObj.get(FIELD_RELEASE); Object tmp = testObj.get(FIELD_SCHEMA); Integer schema = null == tmp ? 0 : Integer.valueOf(tmp.toString()); // get properties Map<String, DBObject> mapProps = getTestRunPropertiesMap(testObjId, runObjId, testName, runName); // get values from the import object Object relObj = importObj.get(FIELD_RELEASE); Object schemaObj = importObj.get(FIELD_SCHEMA); if (null != relObj && !relObj.toString().equals(release)) { result = ImportResult.WARN; message += "\r\nWARN: Release '" + release + "' from test to import doesn't match import release '" + relObj.toString() + "'!"; } if (null != schemaObj && !schemaObj.toString().equals(schema.toString())) { result = ImportResult.WARN; message += "\r\nWARN: Schema '" + schema + "' from test to import doesn't match import schema '" + schemaObj.toString() + "'!"; } // decrypt all values in the properties // separate from set value - might throw exception and nothing should be changed if BasicDBList propsListEnc = (BasicDBList) importObj.get(FIELD_PROPERTIES); BasicDBList propsListDec = new BasicDBList(); for (final Object obj : propsListEnc) { final DBObject dbObj = (DBObject) obj; String propName = (String) dbObj.get(FIELD_NAME); // decrypt DBObject prop = decryptPropertyValue(dbObj, propName); propsListDec.add(prop); } // again a loop and update the values for (final Object objProp : propsListDec) { // get property final DBObject dbObj = (DBObject) objProp; String propName = (String) dbObj.get(FIELD_NAME); // get oldProperty final DBObject oldProp = mapProps.get(propName); if (null == oldProp) { result = ImportResult.WARN; message += "\r\nWARN: Ignored property '" + propName + "' not found"; } else { // see if the value differs String oldValue = getPropValueAsString(oldProp); String newValue = getPropValueAsString(dbObj); if (!oldValue.equals(newValue)) { // update property updateProperty(testName, runName, propName, newValue, oldProp); } } } } catch (ObjectNotFoundException onfe) { message = "Test or test run not found: '" + testName + "." + runName + "'!"; result = ImportResult.ERROR; logger.error(message, onfe); message += "\r\n\r\n" + onfe.toString(); } catch (CipherException ce) { message = "Error during decryption while import properties of test run: '" + testName + "." + runName + "'! No value imported"; result = ImportResult.ERROR; logger.error(message, ce); message += "\r\n\r\n" + ce.toString(); } // put return values resultObj.put(FIELD_RESULT, result.toString()); resultObj.put(FIELD_MESSAGE, message); return resultObj; } /** * Updates a property * * @param testName * (String) test name * @param runName * (String) run name * @param propName * (String) property name to update * @param newValue * (String) value to set * @param oldProp * (DBObject) current property to replace value */ private void updateProperty(String testName, String runName, String propName, String newValue, DBObject oldProp) { // get the default value from the old property first String oldDefault = (String) oldProp.get(FIELD_DEFAULT); // get the version from the old property Object objVersion = oldProp.get(FIELD_VERSION); String oldVersionStr = "0"; if (null != objVersion) { oldVersionStr = objVersion.toString(); } int version = Integer.valueOf(oldVersionStr); // if new value matches default -> delete else update if (oldDefault.equals(newValue)) { newValue = null; } setPropertyOverride(testName, runName, propName, version, newValue); } /** * Returns the value or default of the property as string * * @param dbPropertyObj (DBObject) property to read * * @return Either value (if set) or default (if present) or an empty string */ public String getPropValueAsString(DBObject dbPropertyObj) { ArgumentCheck.checkMandatoryObject(dbPropertyObj, "dbPropertyObj"); String result = ""; Object obj = dbPropertyObj.get(FIELD_VALUE); if (null == obj) { obj = dbPropertyObj.get(FIELD_DEFAULT); } if (null != obj) { result = obj.toString(); } return result; } /** * Exports a test run with encrypted password properties by test and run * name. * * @param testName * (String, mandatory) test name * @param runName * (String, mandatory) run name * * @return (DBObject) or exception * * @throws ObjectNotFoundException * @throws CipherException * * @since 2.1.2 */ public DBObject exportTestRun(String testName, String runName) throws ObjectNotFoundException, CipherException { ArgumentCheck.checkMandatoryString(testName, "testName"); ArgumentCheck.checkMandatoryString(runName, "runName"); // get object IDs ObjectId testObjId = getTestId(testName); ObjectId runObjId = getTestRunId(testObjId, runName); if (null == testObjId) { throw new ObjectNotFoundException(testName + "." + runName); } // get test definition DBObject queryObj = QueryBuilder.start(FIELD_ID).is(testObjId).get(); BasicDBObjectBuilder fieldsObjBuilder = BasicDBObjectBuilder.start(FIELD_NAME, 1).add(FIELD_VERSION, true) .add(FIELD_RELEASE, true).add(FIELD_SCHEMA, true); DBObject fieldsObj = fieldsObjBuilder.get(); DBObject testObj = tests.findOne(queryObj, fieldsObj); if (testObj == null) { throw new ObjectNotFoundException(testName + "." + runName); } // get values from test String release = (String) testObj.get(FIELD_RELEASE); Object tmp = testObj.get(FIELD_SCHEMA); Integer schema = null == tmp ? 0 : Integer.valueOf(tmp.toString()); tmp = testObj.get(FIELD_VERSION); Integer version = null == tmp ? 0 : Integer.valueOf(tmp.toString()); // get properties Set<String> maskedProps = getMaskedProperyNames(testName); Map<String, DBObject> mapProps = getTestRunPropertiesMap(testObjId, runObjId, testName, runName); // encrypt passwords for (final String propName : maskedProps) { DBObject propDbObj = mapProps.get(propName); if (null != propDbObj) { // encrypt propDbObj = encryptPropertyValue(propDbObj, propName); mapProps.put(propName, propDbObj); } } // prepare return object DBObject exportObj = new BasicDBObject(); exportObj.put(FIELD_TEST, testName); exportObj.put(FIELD_RUN, runName); exportObj.put(FIELD_RELEASE, release); exportObj.put(FIELD_SCHEMA, schema); exportObj.put(FIELD_VERSION, version); // Turn into a map and add BasicDBList propsList = MongoTestDAO.getPropertyList(mapProps); exportObj.put(FIELD_PROPERTIES, propsList); return exportObj; } /** * Encrypts property DB object * * @param dbObject * (DBObject, mandatory) * @param propName * (String, mandatory) name of the property * * @return (DBObject) where fields 'FIELD_DEFAULT' and 'FIELD_VALUE' are * encrypted. * * @throws CipherException */ public DBObject encryptPropertyValue(DBObject dbObject, String propName) throws CipherException { ArgumentCheck.checkMandatoryObject(dbObject, "dbObject"); ArgumentCheck.checkMandatoryString(propName, "propName"); // create a copy first DBObject newObj = copyDBObject(dbObject); // get potential value fields Object defObj = dbObject.get(FIELD_DEFAULT); Object valObj = dbObject.get(FIELD_VALUE); if (null != defObj) { String defValue = AESCipher.encode(propName, defObj.toString()); newObj.put(FIELD_DEFAULT, defValue); } if (null != valObj) { String value = AESCipher.encode(propName, valObj.toString()); newObj.put(FIELD_VALUE, value); } // store cipher version newObj.put(FIELD_CIPHER, CipherVersion.V1.toString()); return newObj; } /** * Decrypt property if encrypted * * @param dbObject * (DBObject, mandatory) Property, that might be encrypted * @param propName * (String, mandatory) Name of the property * * @return (DBObject) where fields 'FIELD_DEFAULT' and 'FIELD_VALUE' are no * longer encrypted. * * @throws CipherException */ public DBObject decryptPropertyValue(DBObject dbObject, String propName) throws CipherException { ArgumentCheck.checkMandatoryObject(dbObject, "dbObject"); ArgumentCheck.checkMandatoryString(propName, "propName"); String cipher = (String) dbObject.get(FIELD_CIPHER); if (null == cipher || cipher.isEmpty()) { // not encrypted return dbObject; } // create a copy first DBObject newObj = copyDBObject(dbObject); // get potential value fields Object defObj = dbObject.get(FIELD_DEFAULT); Object valObj = dbObject.get(FIELD_VALUE); CipherVersion version = CipherVersion.valueOf(cipher); switch (version) { case NONE: // nothing to do return dbObject; case V1: if (null != defObj) { String defValue = AESCipher.decode(propName, defObj.toString()); newObj.put(FIELD_DEFAULT, defValue); } if (null != valObj) { String value = AESCipher.decode(propName, valObj.toString()); newObj.put(FIELD_VALUE, value); } break; default: throw new CipherException("Unknown ciper version: '" + cipher + "'"); } return newObj; } }