/**
* Copyright (C) 2008 - 2014 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* - Apache License, version 2.0
* - Apache Software License, version 1.0
* - GNU Lesser General Public License, version 3
* - Mozilla Public License, versions 1.0, 1.1 and 2.0
* - Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* icense version 2 and the aforementioned licenses.
*
* This program 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.
*/
package org.n52.ses.persistency.streams;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import org.n52.ses.api.event.MapEvent;
import org.n52.ses.api.event.PersistedEvent;
import org.n52.ses.api.ws.ISubscriptionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class H2StreamPersistence extends AbstractStreamPersistence {
private Connection connection;
private static final Logger logger = LoggerFactory.getLogger(H2StreamPersistence.class);
private static final String TABLE_NAME = "EVENTS";
private static final String TIMESTAMP_COLUMN = "TIME";
private static final String TIMESTAMP_COLUMN_TYPE = "TIMESTAMP";
private static final String VALUE_COLUMN = "VALUE";
private static final String VALUE_COLUMN_TYPE = "BINARY(256000)";
private static final String STREAM_NAME_COLUMN = "STREAM_NAME";
private static final String STREAM_NAME_COLUMN_TYPE = "VARCHAR(255)";
private static final String ID_COLUMN = "ID";
private int maxEventCount = 10;
@Override
protected void initialize(ISubscriptionManager subMgr, File baseLocation, int maxEvents) throws Exception {
this.maxEventCount = maxEvents;
Class.forName("org.h2.Driver");
String connString = "jdbc:h2:"+baseLocation.getAbsolutePath()+"/streamPersistence/"+subMgr.getUniqueID();
this.connection = DriverManager.getConnection(connString);
this.connection.setAutoCommit(true);
try {
validateTable();
}
catch (IllegalStateException e) {
logger.warn("Stream database in an illegal state", e.getMessage());
createTable();
}
}
private void createTable() throws SQLException {
Statement stmt = this.connection.createStatement();
stmt.execute("CREATE TABLE " +TABLE_NAME+
"("+ ID_COLUMN+" bigint auto_increment, "+
TIMESTAMP_COLUMN +" "+TIMESTAMP_COLUMN_TYPE +", "+
STREAM_NAME_COLUMN +" "+STREAM_NAME_COLUMN_TYPE+", "+
VALUE_COLUMN +" "+VALUE_COLUMN_TYPE+ ")" );
}
private void validateTable() throws SQLException {
DatabaseMetaData md = this.connection.getMetaData();
ResultSet rs = md.getTables(null, null, TABLE_NAME, null);
boolean valid = true;
if (rs.next()) {
rs.close();
rs = md.getColumns(null, null, null, TIMESTAMP_COLUMN);
if (rs.next()) {
rs.close();
}
else {
valid = false;
}
rs = md.getColumns(null, null, null, VALUE_COLUMN);
if (rs.next()) {
rs.close();
}
else {
valid = false;
}
rs = md.getColumns(null, null, null, STREAM_NAME_COLUMN);
if (rs.next()) {
rs.close();
}
else {
valid = false;
}
}
else {
throw new IllegalStateException(TABLE_NAME + " not found.");
}
if (!valid) {
Statement stmt = this.connection.createStatement();
stmt.execute("DROP TABLE " +TABLE_NAME+ " CASCADE");
throw new IllegalStateException("Database malformed.");
}
}
@Override
public synchronized List<PersistedEvent> getPersistedEvents() {
List<PersistedEvent> result = new ArrayList<PersistedEvent>();
Statement stmt;
try {
stmt = this.connection.createStatement();
if (stmt.execute("select "+VALUE_COLUMN+ ", "+STREAM_NAME_COLUMN+" from "+ TABLE_NAME + " ORDER BY "+ID_COLUMN)) {
ResultSet rs = stmt.getResultSet();
while (rs.next()) {
result.add(new PersistedEvent(rs.getString(2),
MapEvent.deserialize(new ByteArrayInputStream(rs.getBytes(1)))));
}
}
} catch (SQLException e) {
logger.warn("could not deserialize event", e);
} catch (IOException e) {
logger.warn("could not deserialize event", e);
}
return result;
}
@Override
public synchronized void persistEvent(MapEvent eve, String streamName) {
logger.debug("Persisting MapEvent: "+eve);
try {
int number = computeRemainingSize();
if (number >= 0) {
removeOldestEvents(number+1);
}
PreparedStatement prep = this.connection.prepareStatement(
"insert into "+ TABLE_NAME +" ("+ TIMESTAMP_COLUMN+ ", "+ STREAM_NAME_COLUMN+ ", "+ VALUE_COLUMN +") values (?,?,?)");
prep.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
prep.setString(2, streamName);
prep.setBytes(3, eve.serialize());
prep.execute();
} catch (SQLException e) {
logger.warn("could not serialize event "+ eve, e);
} catch (IOException e) {
logger.warn("could not serialize event "+ eve, e);
}
}
private int computeRemainingSize() throws SQLException {
Statement stmt = this.connection.createStatement();
stmt.execute("select count(*) from "+TABLE_NAME);
ResultSet rs = stmt.getResultSet();
if (rs.next()) {
int count = rs.getInt(1);
return count - maxEventCount;
}
return 0;
}
private void removeOldestEvents(int count) throws SQLException {
Statement stmt = this.connection.createStatement();
stmt.execute("delete from events where ROWNUM() <= "+count);
}
@Override
public void shutdown() throws Exception {
if (this.connection != null) {
this.connection.close();
logger.info("Connection closed.");
}
else {
logger.info("Connection was already null.");
}
}
@Override
public void destroy() throws Exception {
logger.info("Removing stream persistence database for the calling SubscriptionManager.");
if (this.connection != null) {
Statement stmt = this.connection.createStatement();
stmt.execute("DROP ALL OBJECTS DELETE FILES");
}
shutdown();
}
@Override
public int getMaximumEventCount() {
return maxEventCount;
}
@Override
public void freeResources() {
try {
shutdown();
} catch (Exception e) {
logger.warn("error while freeing resources!", e);
}
}
}