/* * Copyright 2011 Research Studios Austria Forschungsgesellschaft mBH * * This file is part of easyrec. * * easyrec is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * easyrec 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with easyrec. If not, see <http://www.gnu.org/licenses/>. */ package org.easyrec.store.dao.plugin.impl; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.easyrec.model.plugin.LogEntry; import org.easyrec.plugin.container.PluginRegistry; import org.easyrec.plugin.generator.Generator; import org.easyrec.plugin.generator.GeneratorConfiguration; import org.easyrec.plugin.generator.GeneratorConfigurationConstants; import org.easyrec.plugin.model.PluginId; import org.easyrec.plugin.stats.GeneratorStatistics; import org.easyrec.plugin.stats.StatisticsConstants; import org.easyrec.store.dao.plugin.LogEntryDAO; import org.easyrec.utils.spring.store.dao.DaoUtils; import org.easyrec.utils.spring.store.dao.impl.AbstractTableCreatingDAOImpl; import org.easyrec.utils.spring.store.service.sqlscript.SqlScriptService; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.MappingSqlQuery; import org.springframework.jdbc.object.SqlFunction; import org.springframework.jdbc.object.SqlUpdate; import javax.sql.DataSource; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource; import java.io.StringReader; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.Date; import java.util.List; /** * @author patrick */ public class LogEntryDAOMysqlImpl extends AbstractTableCreatingDAOImpl implements LogEntryDAO { private static Log logger = LogFactory.getLog(LogEntryDAOMysqlImpl.class); private static GeneratorConfiguration unmarshalConfiguration(String pluginIdAndVersion, String configurationString, PluginRegistry pluginRegistry) { try { Generator<?, ?> generator = Preconditions.checkNotNull(pluginRegistry.getGenerators().get(PluginId.parsePluginId( pluginIdAndVersion))); StringReader xmlRepresentation = new StringReader(configurationString); StreamSource streamSource = new StreamSource(xmlRepresentation); JAXBContext jaxbContext = JAXBContext.newInstance(generator.getConfigurationClass(), GeneratorConfigurationConstants.CONF_MARSHAL_FAILED.getClass(), GeneratorConfigurationConstants.CONF_UNMARSHAL_FAILED.getClass()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); JAXBElement<? extends GeneratorConfiguration> jaxbElement = unmarshaller.unmarshal(streamSource, generator.getConfigurationClass()); return jaxbElement.getValue(); } catch (Exception e) { logger.warn("Unable to unmarshal configuration", e); return GeneratorConfigurationConstants.CONF_UNMARSHAL_FAILED; } } private static GeneratorStatistics unmarshalStatistics(String pluginIdAndVersion, String statisticsString, PluginRegistry pluginRegistry) { try { Generator<?, ?> generator = Preconditions.checkNotNull( pluginRegistry.getGenerators().get(PluginId.parsePluginId(pluginIdAndVersion))); StringReader xmlRepresentation = new StringReader(statisticsString); StreamSource streamSource = new StreamSource(xmlRepresentation); JAXBContext jaxbContext = JAXBContext.newInstance(generator.getStatisticsClass(), StatisticsConstants.STATS_MARSHAL_FAILED.getClass(), StatisticsConstants.STATS_FORCED_END.getClass(), StatisticsConstants.STATS_UNMARSHAL_FAILED.getClass(), StatisticsConstants.STATS_EXECUTION_FAILED.getClass()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); JAXBElement<? extends GeneratorStatistics> jaxbElement = unmarshaller.unmarshal(streamSource, generator.getStatisticsClass()); return jaxbElement.getValue(); } catch (Exception e) { logger.warn("Unable to unmarshal configuration", e); return StatisticsConstants.STATS_UNMARSHAL_FAILED; } } private static final String FORCED_END_MARSHALED = StatisticsConstants.STATS_FORCED_END.marshal(); private SqlUpdate startEntry; private SqlUpdate endEntry; private SqlUpdate endAllEntries; private MappingSqlQuery<Integer> getRunningTenants; private GetLogEntriesStatement getLogEntries; private GetLogEntriesStatement getLogEntriesWithAssocType; private GetLogEntriesStatement getLogEntriesForTenant; private SqlFunction<Integer> getNumberOfLogEntries; private SqlFunction<Integer> getNumberOfLogEntriesForTenant; private GetLogEntriesStatement getLogEntriesForTenantWithAssocType; private SqlUpdate deleteLogEntries; private SqlFunction<Integer> getComputationDurationForDate; private SqlUpdate deleteLogEntryStatement; protected LogEntryDAOMysqlImpl(DataSource dataSource, SqlScriptService sqlScriptService, PluginRegistry pluginRegistry) { super(sqlScriptService); setDataSource(dataSource); startEntry = new SqlUpdate(dataSource, "INSERT INTO plugin_log(tenantId, pluginId, pluginVersion, startDate, assocTypeId, " + "configuration) VALUES (?, ?, ?, ?, ?, ?)", new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR, Types.BLOB}); startEntry.compile(); endEntry = new SqlUpdate(dataSource, "INSERT INTO plugin_log(tenantId, pluginId, pluginVersion, startDate, assocTypeId, configuration, " + "endDate, statistics) VALUES (?, ?, ?, ?, ?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE endDate = ?, statistics = ?", new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR, Types.BLOB, Types.TIMESTAMP, Types.BLOB, Types.TIMESTAMP, Types.BLOB}); endEntry.compile(); endAllEntries = new SqlUpdate(dataSource, "UPDATE plugin_log SET endDate = ?, statistics = ? WHERE endDate IS NULL", new int[]{Types.TIMESTAMP, Types.BLOB}); endAllEntries.compile(); getRunningTenants = new MappingSqlQuery<Integer>(dataSource, "SELECT tenantId FROM plugin_log WHERE endDate IS NULL") { @Override protected Integer mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getInt("tenantId"); } }; getRunningTenants.compile(); getLogEntries = new GetLogEntriesStatement(dataSource, pluginRegistry, "SELECT * FROM plugin_log ORDER BY startDate DESC, id DESC LIMIT ?, ?"); getLogEntries.declareParameter(new SqlParameter("offset", Types.INTEGER)); getLogEntries.declareParameter(new SqlParameter("limit", Types.INTEGER)); getLogEntries.compile(); getLogEntriesForTenant = new GetLogEntriesStatement(dataSource, pluginRegistry, "SELECT * FROM plugin_log WHERE tenantId = ? ORDER BY startDate DESC, id DESC LIMIT ?, ?"); getLogEntriesForTenant.declareParameter(new SqlParameter("tenantId", Types.INTEGER)); getLogEntriesForTenant.declareParameter(new SqlParameter("offset", Types.INTEGER)); getLogEntriesForTenant.declareParameter(new SqlParameter("limit", Types.INTEGER)); getLogEntriesForTenant.compile(); getLogEntriesWithAssocType = new GetLogEntriesStatement(dataSource, pluginRegistry, "SELECT * FROM plugin_log WHERE assocTypeId = ? ORDER BY startDate DESC, id DESC LIMIT ?, ?"); getLogEntriesWithAssocType.declareParameter(new SqlParameter("assocTypeId", Types.INTEGER)); getLogEntriesWithAssocType.declareParameter(new SqlParameter("offset", Types.INTEGER)); getLogEntriesWithAssocType.declareParameter(new SqlParameter("limit", Types.INTEGER)); getLogEntriesWithAssocType.compile(); getLogEntriesForTenantWithAssocType = new GetLogEntriesStatement(dataSource, pluginRegistry, "SELECT * FROM plugin_log WHERE tenantId = ? AND assocTypeId = ? ORDER BY startDate DESC, id DESC LIMIT ?, ?"); getLogEntriesForTenantWithAssocType.declareParameter(new SqlParameter("tenantId", Types.INTEGER)); getLogEntriesForTenantWithAssocType.declareParameter(new SqlParameter("assocTypeId", Types.INTEGER)); getLogEntriesForTenantWithAssocType.declareParameter(new SqlParameter("offset", Types.INTEGER)); getLogEntriesForTenantWithAssocType.declareParameter(new SqlParameter("limit", Types.INTEGER)); getLogEntriesForTenantWithAssocType.compile(); getNumberOfLogEntries = new SqlFunction<Integer>(dataSource, "SELECT count(*) AS entry_count FROM plugin_log"); getNumberOfLogEntries.compile(); getNumberOfLogEntriesForTenant = new SqlFunction<Integer>(dataSource, "SELECT count(*) AS entry_count FROM plugin_log WHERE tenantId = ?"); getNumberOfLogEntriesForTenant.setResultType(Integer.class); getNumberOfLogEntriesForTenant.declareParameter(new SqlParameter("tenantId", Types.INTEGER)); getNumberOfLogEntriesForTenant.compile(); deleteLogEntries = new SqlUpdate(dataSource, "TRUNCATE plugin_log"); deleteLogEntries.compile(); getComputationDurationForDate = new SqlFunction<Integer>(dataSource, "SELECT sum(timestampdiff(second, startDate, endDate)) AS sum_seconds FROM plugin_log WHERE DATE(endDate) = ?"); getComputationDurationForDate.setResultType(Integer.class); getComputationDurationForDate.declareParameter(new SqlParameter("endDate", Types.DATE)); getComputationDurationForDate.compile(); deleteLogEntryStatement = new SqlUpdate(dataSource, "DELETE FROM plugin_log WHERE tenantId = ? AND pluginId = ? AND pluginVersion = ? AND startDate = ? AND assocTypeId = ?"); deleteLogEntryStatement.declareParameter(new SqlParameter("tenantId", Types.INTEGER)); deleteLogEntryStatement.declareParameter(new SqlParameter("pluginId", Types.VARCHAR)); deleteLogEntryStatement.declareParameter(new SqlParameter("pluginVersion", Types.VARCHAR)); deleteLogEntryStatement.declareParameter(new SqlParameter("startDate", Types.TIMESTAMP)); deleteLogEntryStatement.declareParameter(new SqlParameter("assocTypeId", Types.VARCHAR)); deleteLogEntryStatement.compile(); } @Override public String getDefaultTableName() { return "plugin_log"; } @Override public String getTableCreatingSQLScriptName() { return "classpath:sql/pluginContainer/PluginLog.sql"; } public void startEntry(LogEntry entry) { Preconditions.checkNotNull(entry); Preconditions.checkNotNull(entry.getPluginId()); Preconditions.checkNotNull(entry.getPluginId().getUri()); Preconditions.checkNotNull(entry.getPluginId().getVersion()); Preconditions.checkNotNull(entry.getStartDate()); Preconditions.checkNotNull(entry.getConfiguration()); String serializedConfiguration = entry.getConfiguration().marshal(); startEntry.update(entry.getTenantId(), entry.getPluginId().getUri().toASCIIString(), entry.getPluginId().getVersion().toString(), entry.getStartDate(), entry.getAssocTypeId(), serializedConfiguration); } public void endEntry(LogEntry entry) { Preconditions.checkNotNull(entry); Preconditions.checkNotNull(entry.getPluginId()); Preconditions.checkNotNull(entry.getPluginId().getUri()); Preconditions.checkNotNull(entry.getPluginId().getVersion()); Preconditions.checkNotNull(entry.getStartDate()); Preconditions.checkNotNull(entry.getConfiguration()); Preconditions.checkNotNull(entry.getStatistics()); Preconditions.checkArgument(entry.getStartDate().before(entry.getEndDate()) || entry.getStartDate().equals(entry.getEndDate()), "startDate must be before endDate, or startDate = endDate"); String serializedConfiguration = entry.getConfiguration().marshal(); String serializedStatistics = entry.getStatistics().marshal(); endEntry.update(entry.getTenantId(), entry.getPluginId().getUri().toASCIIString(), entry.getPluginId().getVersion().toString(), entry.getStartDate(), entry.getAssocTypeId(), serializedConfiguration, entry.getEndDate(), serializedStatistics, entry.getEndDate(), serializedStatistics); } public void endAllEntries(Date endDate) { Preconditions.checkNotNull(endDate); endAllEntries.update(endDate, FORCED_END_MARSHALED); } public void endAllEntries() { Date endDate = new Date(); endAllEntries(endDate); } public void deleteEntry(LogEntry entry) { deleteLogEntryStatement.update(entry.getTenantId(), entry.getPluginId().getUri().toASCIIString(), entry.getPluginId().getVersion().toString(), entry.getStartDate(), entry.getAssocTypeId()); } public List<Integer> getRunningTenants() { return getRunningTenants.execute(); } public List<LogEntry> getLogEntries(int offset, int limit) { Preconditions.checkArgument(offset >= 0, "offset must be greater than or equal to 0"); Preconditions.checkArgument(limit >= 0, "limit must be greater than or equal to 0"); return getLogEntries.execute(offset, limit); } public List<LogEntry> getLogEntriesForTenant(int tenantId, int offset, int limit) { Preconditions.checkArgument(offset >= 0, "offset must be greater than or equal to 0"); Preconditions.checkArgument(limit >= 0, "limit must be greater than or equal to 0"); return getLogEntriesForTenant.execute(tenantId, offset, limit); } public List<LogEntry> getLogEntries(int assocTypeId, int offset, int limit) { Preconditions.checkArgument(offset >= 0, "offset must be greater than or equal to 0"); Preconditions.checkArgument(limit >= 0, "limit must be greater than or equal to 0"); return getLogEntriesWithAssocType.execute(assocTypeId, offset, limit); } public List<LogEntry> getLogEntriesForTenant(int tenantId, int assocTypeId, int offset, int limit) { Preconditions.checkArgument(offset >= 0, "offset must be greater than or equal to 0"); Preconditions.checkArgument(limit >= 0, "limit must be greater than or equal to 0"); return getLogEntriesForTenantWithAssocType.execute(tenantId, assocTypeId, offset, limit); } public int getNumberOfLogEntries() { return getNumberOfLogEntries.run(); } public int getNumberOfLogEntriesForTenant(int tenantId) { return getNumberOfLogEntriesForTenant.run(tenantId); } public void deleteLogEntries() { deleteLogEntries.update(); } public int getComputationDurationForDate(Date date) { Preconditions.checkNotNull(date); Integer result = getComputationDurationForDate.findObject(date); return Objects.firstNonNull(result, 0); } public int getComputationDurationForDate() { Date date = new Date(); return getComputationDurationForDate(date); } private static class GetLogEntriesStatement extends MappingSqlQuery<LogEntry> { private PluginRegistry pluginRegistry; public GetLogEntriesStatement(DataSource ds, PluginRegistry pluginRegistry, String sql) { super(ds, sql); this.pluginRegistry = pluginRegistry; } @Override public LogEntry mapRow(ResultSet rs, int rowNum) throws SQLException { int id = rs.getInt("id"); int tenantId = rs.getInt("tenantId"); Date startDate = DaoUtils.getDateIfPresent(rs, "startDate"); Date endDate = DaoUtils.getDateIfPresent(rs, "endDate"); int assocTypeId = rs.getInt("assocTypeId"); String pluginIdStr = rs.getString("pluginId"); String pluginVersionStr = rs.getString("pluginVersion"); String configurationStr = rs.getString("configuration"); String statisticsStr = rs.getString("statistics"); PluginId pluginId = null; try { pluginId = new PluginId(pluginIdStr, pluginVersionStr); } catch (IllegalArgumentException e) { logger.warn("unable to parse pluginId", e); } String pluginIdAndVersion = pluginIdStr + "/" + pluginVersionStr; GeneratorConfiguration configuration = unmarshalConfiguration(pluginIdAndVersion, configurationStr, pluginRegistry); GeneratorStatistics statistics = null; if (statisticsStr != null) statistics = unmarshalStatistics(pluginIdAndVersion, statisticsStr, pluginRegistry); return new LogEntry(id, tenantId, pluginId, startDate, endDate, assocTypeId, configuration, statistics); } } }