![JAR search and dependency download from the Maven repository](/logo.png)
com.axway.ats.log.autodb.DbEventRequestProcessor Maven / Gradle / Ivy
/*
* Copyright 2017 Axway Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.axway.ats.log.autodb;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import com.axway.ats.core.dbaccess.ConnectionPool;
import com.axway.ats.core.dbaccess.DbConnection;
import com.axway.ats.core.dbaccess.DbUtils;
import com.axway.ats.core.dbaccess.mssql.DbConnSQLServer;
import com.axway.ats.core.dbaccess.postgresql.DbConnPostgreSQL;
import com.axway.ats.core.utils.StringUtils;
import com.axway.ats.core.utils.TimeUtils;
import com.axway.ats.log.appenders.ActiveDbAppender;
import com.axway.ats.log.autodb.entities.Run;
import com.axway.ats.log.autodb.entities.Testcase;
import com.axway.ats.log.autodb.events.AddRunMetainfoEvent;
import com.axway.ats.log.autodb.events.AddScenarioMetainfoEvent;
import com.axway.ats.log.autodb.events.CleanupLoadQueueStateEvent;
import com.axway.ats.log.autodb.events.EndCheckpointEvent;
import com.axway.ats.log.autodb.events.EndLoadQueueEvent;
import com.axway.ats.log.autodb.events.EndTestCaseEvent;
import com.axway.ats.log.autodb.events.InsertCheckpointEvent;
import com.axway.ats.log.autodb.events.InsertMessageEvent;
import com.axway.ats.log.autodb.events.InsertSystemStatisticEvent;
import com.axway.ats.log.autodb.events.InsertUserActivityStatisticEvent;
import com.axway.ats.log.autodb.events.JoinTestCaseEvent;
import com.axway.ats.log.autodb.events.RegisterThreadWithLoadQueueEvent;
import com.axway.ats.log.autodb.events.RememberLoadQueueStateEvent;
import com.axway.ats.log.autodb.events.StartCheckpointEvent;
import com.axway.ats.log.autodb.events.StartRunEvent;
import com.axway.ats.log.autodb.events.StartSuiteEvent;
import com.axway.ats.log.autodb.events.StartTestCaseEvent;
import com.axway.ats.log.autodb.events.UpdateRunEvent;
import com.axway.ats.log.autodb.events.UpdateSuiteEvent;
import com.axway.ats.log.autodb.events.UpdateTestcaseEvent;
import com.axway.ats.log.autodb.exceptions.DatabaseAccessException;
import com.axway.ats.log.autodb.exceptions.IncorrectProcessorStateException;
import com.axway.ats.log.autodb.exceptions.LoadQueueAlreadyStartedException;
import com.axway.ats.log.autodb.exceptions.LoggingException;
import com.axway.ats.log.autodb.exceptions.NoSuchLoadQueueException;
import com.axway.ats.log.autodb.exceptions.ThreadAlreadyRegisteredWithLoadQueueException;
import com.axway.ats.log.autodb.exceptions.ThreadNotRegisteredWithLoadQueue;
import com.axway.ats.log.autodb.model.AbstractLoggingEvent;
import com.axway.ats.log.autodb.model.CacheableEvent;
import com.axway.ats.log.autodb.model.EventRequestProcessor;
import com.axway.ats.log.autodb.model.EventRequestProcessorListener;
import com.axway.ats.log.autodb.model.IDbWriteAccess;
import com.axway.ats.log.model.SystemLogLevel;
public class DbEventRequestProcessor implements EventRequestProcessor {
/**
* The configuration for this appender
*/
private DbAppenderConfiguration appenderConfig;
/**
* The connection information
*/
private DbConnection dbConnection;
/**
* The DB access instance
*/
private IDbWriteAccess dbAccess;
/**
* The layout according to which to format events
*/
private Layout layout;
/**
* The current state of the event processor
*/
private EventProcessorState eventProcessorState;
/**
* Listener to be notified when events are processed
*/
private EventRequestProcessorListener listener;
/**
* The name of the machine running this event processor
*/
private String machineName;
/**
* We cache the suites opened by this run, so do not have to query the DB
*
* key = ; value =
*/
private static Map suiteIdsCache = new HashMap();
private boolean isBatchMode;
/*
* When rerunning a testcase, we have to delete the faulty one.
* The main thread passes here the id of the test to be deleted.
*/
private int testcaseToDelete = -1;
/*
* This is a list with all deleted tests.
* We use it in order to skip going to the DB as we know the operation will fail.
*/
private List deletedTestcases = new ArrayList();
/*
* If the current state of the DbEventProcessor could not process UpdateSuiteEvent,
* preserve this event and fire it right after StartSuiteEvent is received
* */
private UpdateSuiteEvent pendingUpdateSuiteEvent = null;
/*
* The UpdateRunEvent, fired from the user.
* */
private UpdateRunEvent userProvidedUpdateRunEvent = null;
/*
* The actual UpdateRunEvent that will be processed and executed
* */
private UpdateRunEvent actualUpdateRunEvent = null;
/*
* While these flag is true, all messages are logged as run messages
* */
private boolean afterSuiteMode = false;
/*
* While these flag is true, all messages are logged as suite messages
* */
private boolean afterClassMode = false;
/*
* While these flag is true, all messages, statistics and checkpoints
* are logged as they have been logged from the testcase, that was ended most recently
* */
private boolean afterMethodMode = false;
/*
* Keeps the ID of the last ended testcase, regardless of the testcase result (e.g. PASSED,FAILED,SKIPPED)
* */
private int lastEndedTestcaseId = -1;
/*
* Keeps the ID of the last ended suite
* */
private int lastEndedSuiteId = -1;
public DbEventRequestProcessor( DbAppenderConfiguration appenderConfig, Layout layout,
boolean isBatchMode ) throws DatabaseAccessException {
this( appenderConfig, layout, null, isBatchMode );
}
public DbEventRequestProcessor( DbAppenderConfiguration appenderConfig, Layout layout,
EventRequestProcessorListener listener,
boolean isBatchMode ) throws DatabaseAccessException {
this.appenderConfig = appenderConfig;
this.isBatchMode = isBatchMode;
if( DbUtils.isMSSQLDatabaseAvailable( appenderConfig.getHost(),
appenderConfig.getDatabase(),
appenderConfig.getUser(),
appenderConfig.getPassword() ) ) {
this.dbConnection = new DbConnSQLServer( appenderConfig.getHost(), appenderConfig.getDatabase(),
appenderConfig.getUser(), appenderConfig.getPassword() );
//create the db access layer
this.dbAccess = new SQLServerDbWriteAccess( ( DbConnSQLServer ) dbConnection, isBatchMode );
} else if ( DbUtils.isPostgreSQLDatabaseAvailable( appenderConfig.getHost(),
appenderConfig.getDatabase(),
appenderConfig.getUser(),
appenderConfig.getPassword() ) ) {
this.dbConnection = new DbConnPostgreSQL( appenderConfig.getHost(), appenderConfig.getDatabase(),
appenderConfig.getUser(), appenderConfig.getPassword() );
//create the db access layer
this.dbAccess = new PGDbWriteAccess( ( DbConnPostgreSQL ) dbConnection, isBatchMode );
} else {
String errMsg = "Neither MSSQL, nor PostgreSQL server at '" + appenderConfig.getHost()
+ "' contains ATS log database with name '" + appenderConfig.getDatabase() + "'.";
throw new DatabaseAccessException( errMsg );
}
this.eventProcessorState = new EventProcessorState();
this.layout = layout;
this.listener = listener;
//get the hostname of the machine
try {
InetAddress addr = InetAddress.getLocalHost();
this.machineName = addr.getHostName();
} catch( UnknownHostException uhe ) {
this.machineName = "unknown host";
}
}
/**
* Set the layout for logging messages
*
* @param layout the layout
*/
public void setLayout( Layout layout ) {
this.layout = layout;
}
public int getRunId() {
return eventProcessorState.getRunId();
}
public int getSuiteId() {
return eventProcessorState.getSuiteId();
}
public String getRunName() {
return eventProcessorState.getRunName();
}
public String getRunUserNote() {
return eventProcessorState.getRunUserNote();
}
public int getTestCaseId() {
return eventProcessorState.getTestCaseId();
}
public void processEventRequest( LogEventRequest eventRequest ) throws LoggingException {
if( testcaseToDelete > 0 ) {
/* Pause for a moment processing the current event.
* This is (delete testcase)event is not coming from the FIFO queue, as we want to process it as
* soon as possible, so any events related to this testcase are directly skipped.
*/
deleteRequestedTestcase();
// now resume the processing of the current event that came from the queue
}
if( isBatchMode && eventRequest == null ) {
// timeout waiting for next event - flush the current cache
dbAccess.flushCache();
return;
}
LoggingEvent event = eventRequest.getEvent();
if( event instanceof AbstractLoggingEvent ) {
AbstractLoggingEvent dbAppenderEvent = ( AbstractLoggingEvent ) event;
if( dbAppenderEvent instanceof UpdateSuiteEvent ) {
try {
dbAppenderEvent.checkIfCanBeProcessed( eventProcessorState );
} catch( IncorrectProcessorStateException e ) {
/* Suite not started yet,
* so save the current event as pending
* and fired it right after StartSuiteEvent is received
*/
pendingUpdateSuiteEvent = ( UpdateSuiteEvent ) dbAppenderEvent;
return;
}
} else if( dbAppenderEvent instanceof UpdateRunEvent ) {
try {
/* Run not started yet.
* We will fire the event after StartRunEvent is received
*/
userProvidedUpdateRunEvent = ( UpdateRunEvent ) dbAppenderEvent;
dbAppenderEvent.checkIfCanBeProcessed( eventProcessorState );
} catch( IncorrectProcessorStateException e ) {
/*
* If we get an exception, do no process the event any further.
* We will fire it after StartRunEvent is received.
* */
return;
}
} else {
//first check if we can process the event at all
dbAppenderEvent.checkIfCanBeProcessed( eventProcessorState );
}
if( isBatchMode && ! ( event instanceof CacheableEvent )
&& eventProcessorState.getLifeCycleState() == LifeCycleState.TEST_CASE_STARTED ) {
// this event can not be cached - flush the current cache
dbAccess.flushCache();
}
switch( dbAppenderEvent.getEventType() ){
case START_RUN:
startRun( ( StartRunEvent ) event, eventRequest.getTimestamp() );
if( userProvidedUpdateRunEvent != null ) {
constructUpdateRunEvent();
updateRun( actualUpdateRunEvent );
}
break;
case END_RUN:
endRun( eventRequest.getTimestamp() );
break;
case UPDATE_RUN:
/*
* By using data from the latest UpdateRunEvent and the current run info,
* construct a pending UpdateRunEvent
* */
constructUpdateRunEvent();
updateRun( actualUpdateRunEvent );
break;
case START_AFTER_SUITE:
afterSuiteMode = true;
break;
case END_AFTER_SUITE:
afterSuiteMode = false;
break;
case ADD_RUN_METAINFO:
addRunMetainfo( ( AddRunMetainfoEvent ) event );
break;
case START_SUITE:
startSuite( ( StartSuiteEvent ) event, eventRequest.getTimestamp() );
if( pendingUpdateSuiteEvent != null ) {
updateSuite( pendingUpdateSuiteEvent );
pendingUpdateSuiteEvent = null;
}
break;
case END_SUITE:
endSuite( eventRequest.getTimestamp() );
break;
case UPDATE_SUITE:
updateSuite( ( UpdateSuiteEvent ) event );
break;
case START_AFTER_CLASS:
afterClassMode = true;
break;
case END_AFTER_CLASS:
afterClassMode = false;
break;
case CLEAR_SCENARIO_METAINFO:
clearScenarioMetainfo();
break;
case ADD_SCENARIO_METAINFO:
addScenarioMetainfo( ( AddScenarioMetainfoEvent ) event );
break;
case UPDATE_TEST_CASE:
updateTestcase( ( UpdateTestcaseEvent ) event, eventRequest.getTimestamp() );
break;
case END_TEST_CASE:
endTestCase( ( EndTestCaseEvent ) event, eventRequest.getTimestamp() );
break;
case START_TEST_CASE:
startTestCase( ( StartTestCaseEvent ) event, eventRequest.getTimestamp() );
break;
case JOIN_TEST_CASE:
joinTestCase( ( JoinTestCaseEvent ) event );
break;
case LEAVE_TEST_CASE:
leaveTestCase();
break;
case START_AFTER_METHOD:
afterMethodMode = true;
break;
case END_AFTER_METHOD:
afterMethodMode = false;
break;
case REMEMBER_LOADQUEUE_STATE:
rememberLoadQueueState( ( RememberLoadQueueStateEvent ) event );
break;
case CLEANUP_LOADQUEUE_STATE:
cleanupLoadQueueState( ( CleanupLoadQueueStateEvent ) event );
break;
case END_LOADQUEUE:
endLoadQueue( ( EndLoadQueueEvent ) event, eventRequest.getTimestamp() );
break;
case REGISTER_THREAD_WITH_LOADQUEUE:
registerThreadWithLoadQueue( ( RegisterThreadWithLoadQueueEvent ) event );
break;
case START_CHECKPOINT:
startCheckpoint( ( StartCheckpointEvent ) event );
break;
case END_CHECKPOINT:
endCheckpoint( ( EndCheckpointEvent ) event );
break;
case INSERT_CHECKPOINT:
insertCheckpoint( ( InsertCheckpointEvent ) event );
break;
case INSERT_SYSTEM_STAT:
insertSystemStatistics( ( InsertSystemStatisticEvent ) event );
break;
case INSERT_USER_ACTIVITY_STAT:
insertUserActivityStatistics( ( InsertUserActivityStatisticEvent ) event );
break;
case INSERT_MESSAGE:
InsertMessageEvent insertMessageEvent = ( InsertMessageEvent ) event;
insertMessage( eventRequest, insertMessageEvent.isEscapeHtml(),
insertMessageEvent.isRunMessage() );
break;
default:
throw new LoggingException( "Unsupported logging event of type: "
+ dbAppenderEvent.getEventType() );
}
} else {
insertMessage( eventRequest, false, false );
}
}
private void startRun( StartRunEvent startRunEvent, long timeStamp ) throws DatabaseAccessException {
// this temporary map must be cleared prior to each run
suiteIdsCache.clear();
int previousRunId = eventProcessorState.getPreviousRunId();
int newRunId;
if( previousRunId == 0 ) {
// run sanity check first
dbAccess.runDbSanityCheck();
// now create a new run for first time in this JVM execution
newRunId = dbAccess.startRun( startRunEvent.getRunName(), startRunEvent.getOsName(),
startRunEvent.getProductName(), startRunEvent.getVersionName(),
startRunEvent.getBuildName(), timeStamp,
startRunEvent.getHostName(), true );
//output the run id in the console, so it can be used for results
System.out.println( TimeUtils.getFormattedDateTillMilliseconds()
+ "*** ATS *** Started a new RUN in Test Explorer's database with id: "
+ newRunId );
} else {
// we already had a run, now we will join to the previous run
// we will update the name of the run only
dbAccess.updateRun( previousRunId, startRunEvent.getRunName(), null, null, null, null, null, null,
true );
newRunId = previousRunId;
//output the run id in the console, so it can be used for results
System.out.println( TimeUtils.getFormattedDateTillMilliseconds()
+ "*** ATS *** Joined an existing RUN in Test Explorer's database with id: "
+ newRunId );
}
eventProcessorState.setRunId( newRunId );
eventProcessorState.setRunName( startRunEvent.getRunName() );
eventProcessorState.setRunUserNote( null );
eventProcessorState.setLifeCycleState( LifeCycleState.RUN_STARTED );
//notify the listener that the run started successfully
if( listener != null ) {
listener.onRunStarted();
}
}
private void endRun( long timeStamp ) throws DatabaseAccessException {
int currentRunId = eventProcessorState.getRunId();
dbAccess.endRun( timeStamp, currentRunId, true );
//set the current appender state
eventProcessorState.setPreviousRunId( currentRunId );
eventProcessorState.setRunId( 0 );
eventProcessorState.setLifeCycleState( LifeCycleState.INITIALIZED );
if( listener != null ) {
listener.onRunFinished();
}
}
private void updateRun( UpdateRunEvent updateRunEvent ) throws DatabaseAccessException {
dbAccess.updateRun( eventProcessorState.getRunId(), updateRunEvent.getRunName(),
updateRunEvent.getOsName(), updateRunEvent.getProductName(),
updateRunEvent.getVersionName(), updateRunEvent.getBuildName(),
updateRunEvent.getUserNote(), updateRunEvent.getHostName(), true );
if( updateRunEvent.getRunName() != null ) {
eventProcessorState.setRunName( updateRunEvent.getRunName() );
}
if( updateRunEvent.getUserNote() != null ) {
eventProcessorState.setRunUserNote( updateRunEvent.getUserNote() );
}
}
private void addRunMetainfo( AddRunMetainfoEvent addRunMetainfoEvent ) throws DatabaseAccessException {
dbAccess.addRunMetainfo( eventProcessorState.getRunId(), addRunMetainfoEvent.getMetaKey(),
addRunMetainfoEvent.getMetaValue(), true );
}
private void startSuite( StartSuiteEvent startSuiteEvent,
long timeStamp ) throws DatabaseAccessException {
String suiteName = startSuiteEvent.getSuiteName();
String packageName = startSuiteEvent.getPackage();
int runId = eventProcessorState.getRunId();
// If there is already a suite with same name in the same run don't open a new one, but
// use the existing suite.
if( suiteIdsCache.containsKey( eventProcessorState.getRunId() + suiteName ) ) {
eventProcessorState.setSuiteId( suiteIdsCache.get( eventProcessorState.getRunId() + suiteName ) );
} else {
int suiteId = dbAccess.startSuite( packageName, suiteName, timeStamp, runId, true );
//set the current suite id
eventProcessorState.setSuiteId( suiteId );
//put the id in the cache
suiteIdsCache.put( runId + suiteName, suiteId );
}
//set the current appender state
eventProcessorState.setLifeCycleState( LifeCycleState.SUITE_STARTED );
}
private void endSuite( long timeStamp ) throws DatabaseAccessException {
try {
dbAccess.endSuite( timeStamp, eventProcessorState.getSuiteId(), true );
lastEndedSuiteId = eventProcessorState.getSuiteId();
} finally {
// even when this DB entity could not finish due to error,
// we want to clear the internal state,
// so next sub-entities do not go into this one
eventProcessorState.setLifeCycleState( LifeCycleState.RUN_STARTED );
eventProcessorState.setSuiteId( 0 );
}
}
private void updateSuite( UpdateSuiteEvent event ) throws DatabaseAccessException {
try {
dbAccess.updateSuite( eventProcessorState.getSuiteId(), event.getSuiteName(), event.getUserNote(),
true );
} finally {
// Due to change in the suite name, update suiteIdCache,
// only if suite name is not null and is not an empty string
if( !StringUtils.isNullOrEmpty( event.getSuiteName() ) ) {
suiteIdsCache.put( eventProcessorState.getRunId() + event.getSuiteName(),
eventProcessorState.getSuiteId() );
}
}
}
private void clearScenarioMetainfo() throws DatabaseAccessException {
dbAccess.clearScenarioMetainfo( eventProcessorState.getTestCaseId(), true );
}
private void
addScenarioMetainfo( AddScenarioMetainfoEvent addScenarioMetainfoEvent ) throws DatabaseAccessException {
dbAccess.addScenarioMetainfo( eventProcessorState.getTestCaseId(),
addScenarioMetainfoEvent.getMetaKey(),
addScenarioMetainfoEvent.getMetaValue(), true );
}
private void startTestCase( StartTestCaseEvent startTestCaseEvent,
long timeStamp ) throws LoggingException {
int currentSuiteId = eventProcessorState.getSuiteId();
String newSuiteName = startTestCaseEvent.getSuiteSimpleName();
if( !StringUtils.isNullOrEmpty( newSuiteName ) ) {
// Due to implementation specifics, it is not possible that a suite with
// the needed name is not in the list of remembered suites (else clause).
// This is guaranteed when we start the suite(which is before we start a testcase)
// we want to make sure we are starting this testcase in a suite with this name
String newSuiteIdentifier = eventProcessorState.getRunId() + newSuiteName;
currentSuiteId = suiteIdsCache.get( newSuiteIdentifier );
}
/*
* This event happens on the Test Executor side.
*/
int testCaseId = dbAccess.startTestCase( startTestCaseEvent.getSuiteFullName(),
startTestCaseEvent.getScenarioName(),
startTestCaseEvent.getScenarioDescription(),
startTestCaseEvent.getTestcaseName(), timeStamp,
currentSuiteId, true );
//set the current appender state
TestCaseState testCaseState = new TestCaseState();
testCaseState.setTestcaseId( testCaseId );
eventProcessorState.setTestCaseState( testCaseState );
eventProcessorState.setLifeCycleState( LifeCycleState.TEST_CASE_STARTED );
//unblock the main thread which is waiting for the completion of this event
if( listener != null ) {
listener.onTestcaseStarted();
}
}
private void endTestCase( EndTestCaseEvent endTestCaseEvent, long timeStamp ) throws LoggingException {
try {
if( !this.deletedTestcases.contains( eventProcessorState.getTestCaseId() ) ) {
dbAccess.endTestCase( endTestCaseEvent.getTestCaseResult().toInt(), timeStamp,
eventProcessorState.getTestCaseId(), true );
lastEndedTestcaseId = eventProcessorState.getTestCaseId();
}
} finally {
// even when this DB entity could not finish due to error,
// we want to clear the internal state,
// so next sub-entities do not go into this one
eventProcessorState.setLifeCycleState( LifeCycleState.SUITE_STARTED );
eventProcessorState.getTestCaseState().clearTestcaseId();
eventProcessorState.getLoadQueuesState().clearAll();
}
}
private void updateTestcase( UpdateTestcaseEvent updateTestcaseEvent, long timestamp ) throws LoggingException {
String suiteFullName = updateTestcaseEvent.getSuiteFullName();
String scenarioName = updateTestcaseEvent.getScenarioName();
String scenarioDescription = updateTestcaseEvent.getScenarioDescription();
String testcaseName = updateTestcaseEvent.getTestcaseName();
String userNote = updateTestcaseEvent.getUserNote();
int testcaseResult = updateTestcaseEvent.getTestcaseResult();
int testcaseId = ( getTestCaseId() == -1 ) ? lastEndedTestcaseId : getTestCaseId();
dbAccess.updateTestcase( suiteFullName, scenarioName, scenarioDescription,
testcaseName, userNote, testcaseResult, testcaseId, timestamp, true );
}
/**
* Remember a testcase that is to be deleted
*
* @param testcaseId
*/
public void requestTestcaseDeletion( int testcaseId ) {
/*
* This code runs on the Test Executor side
*/
this.testcaseToDelete = testcaseId;
}
private void deleteRequestedTestcase() throws DatabaseAccessException {
/*
* This code runs on the Test Executor side
*/
// delete this testcase
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy