/*******************************************************************************
* Copyright 2013-2015 alladin-IT GmbH
*
* 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 at.alladin.rmbt.qos;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import at.alladin.rmbt.controlServer.ErrorList;
import at.alladin.rmbt.db.Client;
import at.alladin.rmbt.db.QoSTestResult;
import at.alladin.rmbt.db.QoSTestResult.TestType;
import at.alladin.rmbt.db.QoSTestTypeDesc;
import at.alladin.rmbt.db.Test;
import at.alladin.rmbt.db.dao.QoSTestDescDao;
import at.alladin.rmbt.db.dao.QoSTestResultDao;
import at.alladin.rmbt.db.dao.QoSTestTypeDescDao;
import at.alladin.rmbt.qos.testscript.TestScriptInterpreter;
import at.alladin.rmbt.shared.hstoreparser.Hstore;
import at.alladin.rmbt.shared.hstoreparser.HstoreParseException;
import at.alladin.rmbt.util.capability.QualityOfServiceCapability;
/**
*
* @author lb
*
*/
public class QoSUtil {
public static final Hstore HSTORE_PARSER = new Hstore(HttpProxyResult.class, NonTransparentProxyResult.class,
DnsResult.class, TcpResult.class, UdpResult.class, WebsiteResult.class, VoipResult.class, TracerouteResult.class);
/**
*
* @author lb
*
*/
public static class TestUuid {
public static enum UuidType {
TEST_UUID, OPEN_TEST_UUID
}
protected UuidType type;
protected String uuid;
public TestUuid(String uuid, UuidType type) {
this.type = type;
this.uuid = uuid;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public UuidType getType() {
return type;
}
public void setType(UuidType type) {
this.type = type;
}
}
/**
*
* @param settings
* @param conn
* @param answer
* @param lang
* @param errorList
* @throws SQLException
* @throws JSONException
* @throws HstoreParseException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public static void evaluate(final ResourceBundle settings, final Connection conn, final TestUuid uuid,
final JSONObject answer, String lang, final ErrorList errorList, final QualityOfServiceCapability qosCapability) throws SQLException, HstoreParseException, JSONException, IllegalArgumentException, IllegalAccessException {
// Load Language Files for Client
final List<String> langs = Arrays.asList(settings.getString("RMBT_SUPPORTED_LANGUAGES").split(",\\s*"));
if (langs.contains(lang)) {
errorList.setLanguage(lang);
}
else {
lang = settings.getString("RMBT_DEFAULT_LANGUAGE");
}
if (conn != null)
{
final Client client = new Client(conn);
final Test test = new Test(conn);
boolean necessaryDataAvailable = false;
if (uuid != null && uuid.getType() != null && uuid.getUuid() != null) {
switch (uuid.getType()) {
case OPEN_TEST_UUID:
if (test.getTestByOpenTestUuid(UUID.fromString(uuid.getUuid())) > 0
&& client.getClientByUid(test.getField("client_id").intValue())) {
necessaryDataAvailable = true;
}
break;
case TEST_UUID:
if (test.getTestByUuid(UUID.fromString(uuid.getUuid())) > 0
&& client.getClientByUid(test.getField("client_id").intValue())) {
necessaryDataAvailable = true;
}
break;
}
}
final long timeStampFullEval = System.currentTimeMillis();
if (necessaryDataAvailable)
{
final Locale locale = new Locale(lang);
final ResultOptions resultOptions = new ResultOptions(locale);
final JSONArray resultList = new JSONArray();
QoSTestResultDao resultDao = new QoSTestResultDao(conn);
List<QoSTestResult> testResultList = resultDao.getByTestUid(test.getUid());
if (testResultList == null || testResultList.isEmpty()) {
throw new UnsupportedOperationException("test " + test + " has no result list");
}
//map that contains all test types and their result descriptions determined by the test result <-> test objectives comparison
Map<TestType,TreeSet<ResultDesc>> resultKeys = new HashMap<>();
//test description set:
Set<String> testDescSet = new TreeSet<>();
//test summary set:
Set<String> testSummarySet = new TreeSet<>();
//Staring timestamp for evaluation time measurement
final long timeStampEval = System.currentTimeMillis();
//iterate through all result entries
for (final QoSTestResult testResult : testResultList) {
//reset test counters
testResult.setFailureCounter(0);
testResult.setSuccessCounter(0);
//get the correct class of the result;
TestType testType = null;
try {
testType = TestType.valueOf(testResult.getTestType().toUpperCase(Locale.US));
}
catch(IllegalArgumentException e) {
final String errorMessage = "WARNING: QoS TestType '" + testResult.getTestType().toUpperCase(Locale.US) + "' not supported by ControlServer. Test with UID: " + testResult.getUid() + " skipped.";
System.out.println(errorMessage);
errorList.addErrorString(errorMessage);
testType = null;
}
if (testType == null) {
continue;
}
Class<? extends AbstractResult<?>> clazz = testType.getClazz();
//parse hstore data
if (testResult.getResults() != null) {
final JSONObject resultJson = new JSONObject(testResult.getResults());
AbstractResult<?> result = QoSUtil.HSTORE_PARSER.fromJSON(resultJson, clazz);
result.setResultJson(resultJson);
if (result != null) {
//add each test description key to the testDescSet (to fetch it later from the db)
if (testResult.getTestDescription() != null) {
testDescSet.add(testResult.getTestDescription());
}
if (testResult.getTestSummary() != null) {
testSummarySet.add(testResult.getTestSummary());
}
testResult.setResult(result);
}
//compare test results
compareTestResults(testResult, result, resultKeys, testType, resultOptions);
}
//resultList.put(testResult.toJson());
//save all test results after the success and failure counters have been set
//resultDao.updateCounter(testResult);
}
//ending timestamp for evaluation time measurement
final long timeStampEvalEnd = System.currentTimeMillis();
//-------------------------------------------------------------
//fetch all result strings from the db
QoSTestDescDao descDao = new QoSTestDescDao(conn, locale);
//FIRST: get all test descriptions
Set<String> testDescToFetchSet = testDescSet;
testDescToFetchSet.addAll(testSummarySet);
Map<String, String> testDescMap = descDao.getAllByKeyToMap(testDescToFetchSet);
for (QoSTestResult testResult : testResultList) {
//and set the test results + put each one to the result list json array
String preParsedDesc = testDescMap.get(testResult.getTestDescription());
if (preParsedDesc != null) {
String description = String.valueOf(TestScriptInterpreter.interprete(testDescMap.get(testResult.getTestDescription()),
QoSUtil.HSTORE_PARSER, testResult.getResult(), true, resultOptions));
testResult.setTestDescription(description);
}
//do the same for the test summary:
String preParsedSummary = testDescMap.get(testResult.getTestSummary());
if (preParsedSummary != null) {
String description = String.valueOf(TestScriptInterpreter.interprete(testDescMap.get(testResult.getTestSummary()),
QoSUtil.HSTORE_PARSER, testResult.getResult(), true, resultOptions));
testResult.setTestSummary(description);
}
final JSONObject resultJsonObject = testResult.toJson(uuid.getType());
if (resultJsonObject != null) {
resultList.put(resultJsonObject);
}
}
//finally put results to json
if (resultList != null && resultList.length() > 0) {
answer.put("testresultdetail", resultList);
}
JSONArray resultDescArray = new JSONArray();
//SECOND: fetch all test result descriptions
for (TestType testType : resultKeys.keySet()) {
TreeSet<ResultDesc> descSet = resultKeys.get(testType);
//fetch results to same object
descDao.loadToTestDesc(descSet);
//another tree set for duplicate entries:
//TODO: there must be a better solution
//(the issue is: compareTo() method returns differnt values depending on the .value attribute (if it's set or not))
TreeSet<ResultDesc> descSetNew = new TreeSet<>();
//add fetched results to json
for (ResultDesc desc : descSet) {
if (!qosCapability.isSupportsInfo()) {
if (ResultDesc.STATUS_CODE_INFO.equals(desc.getStatusCode())) {
continue;
}
}
if (!descSetNew.contains(desc)) {
descSetNew.add(desc);
}
else {
for (ResultDesc d : descSetNew) {
if (d.compareTo(desc) == 0) {
d.getTestResultUidList().addAll(desc.getTestResultUidList());
}
}
}
}
for (ResultDesc desc : descSetNew) {
if (desc.getValue() != null) {
resultDescArray.put(desc.toJson());
}
}
}
//System.out.println(resultDescArray);
//put result descriptions to json
answer.put("testresultdetail_desc", resultDescArray);
QoSTestTypeDescDao testTypeDao = new QoSTestTypeDescDao(conn, locale);
JSONArray testTypeDescArray = new JSONArray();
for (QoSTestTypeDesc desc : testTypeDao.getAll()) {
final JSONObject testTypeDesc = desc.toJson();
if (testTypeDesc != null) {
testTypeDescArray.put(testTypeDesc);
}
}
//put result descriptions to json
answer.put("testresultdetail_testdesc", testTypeDescArray);
JSONObject evalTimes = new JSONObject();
evalTimes.put("eval", (timeStampEvalEnd - timeStampEval));
evalTimes.put("full", (System.currentTimeMillis() - timeStampFullEval));
answer.put("eval_times", evalTimes);
//System.out.println(answer);
}
else
errorList.addError("ERROR_REQUEST_TEST_RESULT_DETAIL_NO_UUID");
}
else
errorList.addError("ERROR_DB_CONNECTION");
}
/**
* compares test results with expected results and increases success/failure counter
* @param testResult the test result
* @param result the parsed test result
* @param resultKeys result key map
* @param testType test type
* @param resultOptions result options
* @throws HstoreParseException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static void compareTestResults(final QoSTestResult testResult, final AbstractResult<?> result,
final Map<TestType,TreeSet<ResultDesc>> resultKeys, final TestType testType, final ResultOptions resultOptions) throws HstoreParseException, IllegalArgumentException, IllegalAccessException {
//if expected resuls not null, compare them to the test results
if (testResult.getExpectedResults()!=null) {
final Class<? extends AbstractResult<?>> clazz = testType.getClazz();
//create a parsed abstract result set sorted by priority
final Set<AbstractResult<?>> expResultSet = new TreeSet<AbstractResult<?>>(new Comparator<AbstractResult<?>>() {
@Override
public int compare(final AbstractResult<?> o1, final AbstractResult<?> o2) {
return o1.priority.compareTo(o2.priority);
}
});
int priority = Integer.MAX_VALUE;
if (testResult.getExpectedResults() != null) {
for (int i = 0; i < testResult.getExpectedResults().length(); i++) {
final JSONObject expectedResults = testResult.getExpectedResults().optJSONObject(i);
if (expectedResults != null) {
//parse hstore string to object
final AbstractResult<?> expResult = QoSUtil.HSTORE_PARSER.fromJSON(expectedResults, clazz);
if (expResult.getPriority() == Integer.MAX_VALUE) {
expResult.setPriority(priority--);
}
expResultSet.add(expResult);
}
}
}
for (final AbstractResult<?> expResult : expResultSet) {
//compare expected result to test result and save the returned id
ResultDesc resultDesc = ResultComparer.compare(result, expResult, QoSUtil.HSTORE_PARSER, resultOptions);
if (resultDesc != null) {
resultDesc.addTestResultUid(testResult.getUid());
resultDesc.setTestType(testType);
final ResultHolder resultHolder = calculateResultCounter(testResult, expResult, resultDesc);
//check if there is a result message
if (resultHolder != null) {
TreeSet<ResultDesc> resultDescSet;
if (resultKeys.containsKey(testType)) {
resultDescSet = resultKeys.get(testType);
}
else {
resultDescSet = new TreeSet<>();
resultKeys.put(testType, resultDescSet);
}
resultDescSet.add(resultDesc);
testResult.getResultKeyMap().put(resultDesc.getKey(), resultHolder.resultKeyType);
if (AbstractResult.BEHAVIOUR_ABORT.equals(resultHolder.event)) {
break;
}
}
}
}
}
}
/**
* calculates and set the specific result counter
* @param testResult test result
* @param expResult expected test result
* @param resultDesc result description
* @return result type string, can be:
* <ul>
* <li>{@link ResultDesc#STATUS_CODE_SUCCESS}</li>
* <li>{@link ResultDesc#STATUS_CODE_FAILURE}</li>
* <li>{@link ResultDesc#STATUS_CODE_INFO}</li>
* </ul>
*/
public static ResultHolder calculateResultCounter(final QoSTestResult testResult, final AbstractResult<?> expResult, final ResultDesc resultDesc) {
String resultKeyType = null;
String event = AbstractResult.BEHAVIOUR_NOTHING;
//increase the failure or success counter of this result object
if (resultDesc.getStatusCode().equals(ResultDesc.STATUS_CODE_SUCCESS)) {
if (expResult.getOnSuccess() != null) {
testResult.setSuccessCounter(testResult.getSuccessCounter()+1);
if (AbstractResult.RESULT_TYPE_DEFAULT.equals(expResult.getSuccessType())) {
resultKeyType = ResultDesc.STATUS_CODE_SUCCESS;
}
else {
resultKeyType = ResultDesc.STATUS_CODE_INFO;
}
event = expResult.getOnSuccessBehaivour();
}
}
else if (resultDesc.getStatusCode().equals(ResultDesc.STATUS_CODE_FAILURE)) {
if (expResult.getOnFailure() != null) {
testResult.setFailureCounter(testResult.getFailureCounter()+1);
if (AbstractResult.RESULT_TYPE_DEFAULT.equals(expResult.getFailureType())) {
resultKeyType = ResultDesc.STATUS_CODE_FAILURE;
}
else {
resultKeyType = ResultDesc.STATUS_CODE_INFO;
}
event = expResult.getOnFailureBehaivour();
}
}
else {
resultKeyType = ResultDesc.STATUS_CODE_INFO;
event = AbstractResult.BEHAVIOUR_NOTHING;
}
return resultKeyType != null ? new ResultHolder(resultKeyType, event) : null;
}
/**
*
* @author lb
*
*/
public static class ResultHolder {
final String resultKeyType;
final String event;
public ResultHolder(final String resultKeyType, final String event) {
this.resultKeyType = resultKeyType;
this.event = event;
}
public String getResultKeyType() {
return resultKeyType;
}
public String getEvent() {
return event;
}
}
}