/** * This file is part of General Entity Annotator Benchmark. * * General Entity Annotator Benchmark 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. * * General Entity Annotator Benchmark 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 General Entity Annotator Benchmark. If not, see <http://www.gnu.org/licenses/>. */ package org.aksw.gerbil.database; import java.io.IOException; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Arrays; import java.util.List; import javax.sql.DataSource; import org.aksw.gerbil.config.GerbilConfiguration; import org.aksw.gerbil.datatypes.ErrorTypes; import org.aksw.gerbil.datatypes.ExperimentTaskResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; /** * SQL database based implementation of the {@link AbstractExperimentDAO} class. * * @author b.eickmann * @author m.roeder * */ public class ExperimentDAOImpl extends AbstractExperimentDAO { private static final Logger LOGGER = LoggerFactory.getLogger(ExperimentDAOImpl.class); private final static String INSERT_TASK = "INSERT INTO ExperimentTasks (annotatorName, datasetName, experimentType, matching, state, lastChanged) VALUES (:annotatorName, :datasetName, :experimentType, :matching, :state, :lastChanged)"; private final static String SET_TASK_STATE = "UPDATE ExperimentTasks SET state=:state, lastChanged=:lastChanged WHERE id=:id"; private final static String SET_EXPERIMENT_TASK_RESULT = "UPDATE ExperimentTasks SET microF1=:microF1 , microPrecision=:microPrecision, microRecall=:microRecall, macroF1=:macroF1, macroPrecision=:macroPrecision, macroRecall=:macroRecall, errorCount=:errorCount, lastChanged=:lastChanged WHERE id=:id"; private final static String CONNECT_TASK_EXPERIMENT = "INSERT INTO Experiments (id, taskId) VALUES(:id, :taskId)"; private final static String GET_TASK_STATE = "SELECT state FROM ExperimentTasks WHERE id=:id"; private final static String GET_EXPERIMENT_RESULTS = "SELECT annotatorName, datasetName, experimentType, matching, microF1, microPrecision, microRecall, macroF1, macroPrecision, macroRecall, state, errorCount, lastChanged, taskId FROM ExperimentTasks t, Experiments e WHERE e.id=:id AND e.taskId=t.id"; private final static String GET_EXPERIMENT_TASK_RESULT = "SELECT annotatorName, datasetName, experimentType, matching, microF1, microPrecision, microRecall, macroF1, macroPrecision, macroRecall, state, errorCount, lastChanged, id FROM ExperimentTasks t WHERE id=:id"; private final static String GET_CACHED_TASK = "SELECT id FROM ExperimentTasks WHERE annotatorName=:annotatorName AND datasetName=:datasetName AND experimentType=:experimentType AND matching=:matching AND lastChanged>:lastChanged AND state>:errorState ORDER BY lastChanged DESC LIMIT 1"; private final static String GET_HIGHEST_EXPERIMENT_ID = "SELECT id FROM Experiments ORDER BY id DESC LIMIT 1"; private final static String SET_UNFINISHED_TASK_STATE = "UPDATE ExperimentTasks SET state=:state, lastChanged=:lastChanged WHERE state=:unfinishedState"; @Deprecated private final static String GET_LATEST_EXPERIMENT_TASKS = "SELECT DISTINCT annotatorName, datasetName FROM ExperimentTasks WHERE experimentType=:experimentType AND matching=:matching"; @Deprecated private final static String GET_LATEST_EXPERIMENT_TASK_RESULT = "SELECT annotatorName, datasetName, experimentType, matching, microF1, microPrecision, microRecall, macroF1, macroPrecision, macroRecall, state, errorCount, lastChanged FROM ExperimentTasks WHERE annotatorName=:annotatorName AND datasetName=:datasetName AND experimentType=:experimentType AND matching=:matching AND state<>:unfinishedState ORDER BY lastChanged DESC LIMIT 1"; private final static String GET_LATEST_EXPERIMENT_TASK_RESULTS = "SELECT tasks.annotatorName, tasks.datasetName, tasks.experimentType, tasks.matching, tasks.microF1, tasks.microPrecision, tasks.microRecall, tasks.macroF1, tasks.macroPrecision, tasks.macroRecall, tasks.state, tasks.errorCount, tasks.lastChanged, tasks.id FROM ExperimentTasks tasks, (SELECT datasetName, annotatorName, MAX(lastChanged) AS lastChanged FROM ExperimentTasks WHERE experimentType=:experimentType AND matching=:matching AND state<>:unfinishedState AND annotatorName IN (:annotatorNames) AND datasetName IN (:datasetNames) GROUP BY datasetName, annotatorName) pairs WHERE tasks.annotatorName=pairs.annotatorName AND tasks.datasetName=pairs.datasetName AND tasks.experimentType=:experimentType AND tasks.matching=:matching AND tasks.lastChanged=pairs.lastChanged"; private final static String GET_RUNNING_EXPERIMENT_TASKS = "SELECT annotatorName, datasetName, experimentType, matching, microF1, microPrecision, microRecall, macroF1, macroPrecision, macroRecall, state, errorCount, lastChanged FROM ExperimentTasks WHERE state=:unfinishedState"; private final static String SHUTDOWN = "SHUTDOWN"; private final static String GET_ADDITIONAL_RESULTS = "SELECT resultId, value FROM ExperimentTasks_AdditionalResults WHERE taskId=:taskId"; private final static String INSERT_ADDITIONAL_RESULT = "INSERT INTO ExperimentTasks_AdditionalResults(taskId, resultId, value) VALUES (:taskId, :resultId, :value)"; private final static String GET_SUB_TASK_RESULTS = "SELECT annotatorName, datasetName, experimentType, matching, microF1, microPrecision, microRecall, macroF1, macroPrecision, macroRecall, state, errorCount, lastChanged, subTaskId FROM ExperimentTasks t, ExperimentTasks_SubTasks s WHERE s.taskId=:taskId AND s.subTaskId=t.id"; private final static String INSERT_SUB_TASK_RELATION = "INSERT INTO ExperimentTasks_SubTasks(taskId, subTaskId) VALUES (:taskId, :subTaskId)"; // FIXME remove the following two statements by removing the experiment task // version workaround private final static String GET_VERSION_OF_EXPERIMENT_TASK = "SELECT version FROM ExperimentTasks_Version WHERE id=:id"; private final static String INSERT_VERSION_OF_EXPERIMENT_TASK = "INSERT INTO ExperimentTasks_Version (id, version) VALUES(:id,:version)"; private final NamedParameterJdbcTemplate template; public ExperimentDAOImpl(DataSource dataSource) { this.template = new NamedParameterJdbcTemplate(dataSource); } public ExperimentDAOImpl(DataSource dataSource, long resultDurability) { super(resultDurability); this.template = new NamedParameterJdbcTemplate(dataSource); } @Override public List<ExperimentTaskResult> getResultsOfExperiment(String experimentId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentId); List<ExperimentTaskResult> result = this.template.query(GET_EXPERIMENT_RESULTS, parameters, new ExperimentTaskResultRowMapper()); // FIXME remove this ugly workaround regarding the version of an // experiment task for (ExperimentTaskResult e : result) { addVersion(e); addAdditionalResults(e); addSubTasks(e); } return result; } // FIXME remove this method and implement a better version handling private void addVersion(ExperimentTaskResult result) { result.gerbilVersion = getVersion(result.idInDb); if (result.gerbilVersion == null) { result.gerbilVersion = "1.0.0"; } } // FIXME remove this method and implement a better version handling private String getVersion(int experimentTaskId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentTaskId); List<String> result = this.template.query(GET_VERSION_OF_EXPERIMENT_TASK, parameters, new StringRowMapper()); if (result.size() > 0) { return result.get(0); } else { return null; } } // FIXME remove this method and implement a better version handling private void setVersion(int experimentTaskId) { String version = GerbilConfiguration.getGerbilVersion(); if (version == null) { LOGGER.error("Couldn't get the current gerbil version. Can't add it to the experiment task #" + experimentTaskId + ". Returning."); return; } MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentTaskId); parameters.addValue("version", version); this.template.update(INSERT_VERSION_OF_EXPERIMENT_TASK, parameters); } @Override public int createTask(String annotatorName, String datasetName, String experimentType, String matching, String experimentId) { MapSqlParameterSource params = createTaskParameters(annotatorName, datasetName, experimentType, matching); params.addValue("state", ExperimentDAO.TASK_STARTED_BUT_NOT_FINISHED_YET); java.util.Date today = new java.util.Date(); params.addValue("lastChanged", new java.sql.Timestamp(today.getTime())); params.addValue("version", GerbilConfiguration.getGerbilVersion()); KeyHolder keyHolder = new GeneratedKeyHolder(); this.template.update(INSERT_TASK, params, keyHolder); Integer generatedKey = (Integer) keyHolder.getKey(); if (experimentId != null) { connectToExperiment(experimentId, generatedKey); } // FIXME remove this method and implement a better version handling setVersion(generatedKey); return generatedKey; } private void connectToExperiment(String experimentId, Integer taskId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentId); parameters.addValue("taskId", taskId); this.template.update(CONNECT_TASK_EXPERIMENT, parameters); } private MapSqlParameterSource createTaskParameters(String annotatorName, String datasetName, String experimentType, String matching) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("annotatorName", annotatorName); parameters.addValue("datasetName", datasetName); parameters.addValue("experimentType", experimentType); parameters.addValue("matching", matching); return parameters; } @Override public void setExperimentTaskResult(int experimentTaskId, ExperimentTaskResult result) { // Note that we have to set the state first if we want to override the // automatic timestamp with the one from the // result object setExperimentState(experimentTaskId, result.state); MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentTaskId); parameters.addValue("microF1", result.getMicroF1Measure()); parameters.addValue("microPrecision", result.getMicroPrecision()); parameters.addValue("microRecall", result.getMicroRecall()); parameters.addValue("macroF1", result.getMacroF1Measure()); parameters.addValue("macroPrecision", result.getMacroPrecision()); parameters.addValue("macroRecall", result.getMacroRecall()); parameters.addValue("errorCount", result.getErrorCount()); parameters.addValue("lastChanged", new java.sql.Timestamp(result.timestamp)); this.template.update(SET_EXPERIMENT_TASK_RESULT, parameters); if (result.hasAdditionalResults()) { for (int i = 0; i < result.additionalResults.allocated.length; ++i) { if ((result.additionalResults.allocated[i]) && (result.additionalResults.keys[i] >= 6)) { addAdditionaResult(experimentTaskId, result.additionalResults.keys[i], result.additionalResults.values[i]); } } } if (result.hasSubTasks()) { for (ExperimentTaskResult subTask : result.getSubTasks()) { insertSubTask(subTask, experimentTaskId); } } } protected void addAdditionaResult(int taskId, int resultId, double value) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("taskId", taskId); parameters.addValue("resultId", resultId); parameters.addValue("value", value); this.template.update(INSERT_ADDITIONAL_RESULT, parameters); } @Override public void setExperimentState(int experimentTaskId, int state) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentTaskId); parameters.addValue("state", state); java.util.Date today = new java.util.Date(); parameters.addValue("lastChanged", new java.sql.Timestamp(today.getTime())); this.template.update(SET_TASK_STATE, parameters); } @Override public int getExperimentState(int experimentTaskId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentTaskId); List<Integer> result = this.template.query(GET_TASK_STATE, parameters, new IntegerRowMapper()); if (result.size() > 0) { return result.get(0); } else { return TASK_NOT_FOUND; } } @Override protected int getCachedExperimentTaskId(String annotatorName, String datasetName, String experimentType, String matching) { MapSqlParameterSource params = createTaskParameters(annotatorName, datasetName, experimentType, matching); java.util.Date today = new java.util.Date(); params.addValue("lastChanged", new java.sql.Timestamp(today.getTime() - this.resultDurability)); params.addValue("errorState", ErrorTypes.HIGHEST_ERROR_CODE); List<Integer> result = this.template.query(GET_CACHED_TASK, params, new IntegerRowMapper()); if (result.size() > 0) { return result.get(0); } else { return EXPERIMENT_TASK_NOT_CACHED; } } @Override protected void connectExistingTaskWithExperiment(int experimentTaskId, String experimentId) { connectToExperiment(experimentId, experimentTaskId); } @Override public String getHighestExperimentId() { List<String> result = this.template.query(GET_HIGHEST_EXPERIMENT_ID, new StringRowMapper()); if (result.size() > 0) { return result.get(0); } else { return null; } } @Override protected void setRunningExperimentsToError() { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("unfinishedState", TASK_STARTED_BUT_NOT_FINISHED_YET); parameters.addValue("state", ErrorTypes.SERVER_STOPPED_WHILE_PROCESSING.getErrorCode()); java.util.Date today = new java.util.Date(); parameters.addValue("lastChanged", new java.sql.Timestamp(today.getTime())); this.template.update(SET_UNFINISHED_TASK_STATE, parameters); } @Deprecated @Override protected List<String[]> getAnnotatorDatasetCombinations(String experimentType, String matching) { MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("experimentType", experimentType); params.addValue("matching", matching); return this.template.query(GET_LATEST_EXPERIMENT_TASKS, params, new StringArrayRowMapper(new int[] { 1, 2 })); } @Deprecated @Override protected ExperimentTaskResult getLatestExperimentTaskResult(String experimentType, String matching, String annotatorName, String datasetName) { MapSqlParameterSource params = createTaskParameters(annotatorName, datasetName, experimentType, matching); params.addValue("unfinishedState", TASK_STARTED_BUT_NOT_FINISHED_YET); List<ExperimentTaskResult> result = this.template.query(GET_LATEST_EXPERIMENT_TASK_RESULT, params, new ExperimentTaskResultRowMapper()); if (result.size() > 0) { return result.get(0); } else { return null; } } @Override public List<ExperimentTaskResult> getAllRunningExperimentTasks() { MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("unfinishedState", TASK_STARTED_BUT_NOT_FINISHED_YET); return this.template.query(GET_RUNNING_EXPERIMENT_TASKS, params, new ExperimentTaskResultRowMapper()); } @Override public List<ExperimentTaskResult> getLatestResultsOfExperiments(String experimentType, String matching) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("experimentType", experimentType); parameters.addValue("matching", matching); parameters.addValue("unfinishedState", TASK_STARTED_BUT_NOT_FINISHED_YET); List<ExperimentTaskResult> results = this.template.query(GET_LATEST_EXPERIMENT_TASK_RESULTS, parameters, new ExperimentTaskResultRowMapper()); // FIXME remove this ugly workaround regarding the version of an // experiment task // We had to took this part out, because it needs to much time and the // version isn't used inside the overview // for (ExperimentTaskResult e : result) { // addVersion(e); // } for (ExperimentTaskResult result : results) { addAdditionalResults(result); } return results; } @Override public List<ExperimentTaskResult> getLatestResultsOfExperiments(String experimentType, String matching, String annotatorNames[], String datasetNames[]) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("experimentType", experimentType); parameters.addValue("matching", matching); parameters.addValue("unfinishedState", TASK_STARTED_BUT_NOT_FINISHED_YET); parameters.addValue("annotatorNames", Arrays.asList(annotatorNames)); parameters.addValue("datasetNames", Arrays.asList(datasetNames)); List<ExperimentTaskResult> results = this.template.query(GET_LATEST_EXPERIMENT_TASK_RESULTS, parameters, new ExperimentTaskResultRowMapper()); // FIXME remove this ugly workaround regarding the version of an // experiment task // We had to took this part out, because it needs to much time and the // version isn't used inside the overview // for (ExperimentTaskResult e : result) { // addVersion(e); // } for (ExperimentTaskResult result : results) { addAdditionalResults(result); } return results; } protected void addAdditionalResults(ExperimentTaskResult result) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("taskId", result.idInDb); List<IntDoublePair> addResults = this.template.query(GET_ADDITIONAL_RESULTS, parameters, new IntDoublePairRowMapper()); for (IntDoublePair a : addResults) { result.addAdditionalResult(a.first, a.second); } } protected void insertSubTask(ExperimentTaskResult subTask, int experimentTaskId) { subTask.idInDb = createTask(subTask.annotator, subTask.dataset, subTask.type.name(), subTask.matching.name(), null); setExperimentTaskResult(subTask.idInDb, subTask); addSubTaskRelation(experimentTaskId, subTask.idInDb); } protected void addSubTaskRelation(int taskId, int subTaskId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("taskId", taskId); parameters.addValue("subTaskId", subTaskId); this.template.update(INSERT_SUB_TASK_RELATION, parameters); } protected void addSubTasks(ExperimentTaskResult expTask) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("taskId", expTask.idInDb); List<ExperimentTaskResult> subTasks = this.template.query(GET_SUB_TASK_RESULTS, parameters, new ExperimentTaskResultRowMapper()); expTask.setSubTasks(subTasks); for (ExperimentTaskResult subTask : subTasks) { subTask.gerbilVersion = expTask.gerbilVersion; addAdditionalResults(subTask); } } @Override public ExperimentTaskResult getResultOfExperimentTask(int experimentTaskId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("id", experimentTaskId); List<ExperimentTaskResult> results = this.template.query(GET_EXPERIMENT_TASK_RESULT, parameters, new ExperimentTaskResultRowMapper()); if (results.size() == 0) { return null; } ExperimentTaskResult result = results.get(0); // FIXME remove this ugly workaround regarding the version of an // experiment task addVersion(result); addAdditionalResults(result); addSubTasks(result); return result; } @Override public void close() throws IOException { this.template.execute(SHUTDOWN, new PreparedStatementCallback<Object>() { @Override public Object doInPreparedStatement(PreparedStatement arg0) throws SQLException, DataAccessException { // nothing to do return null; } }); } }