/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2016, Ecole Polytechnique Federale de Lausanne (EPFL)
*
* This file is part of GSN.
*
* GSN 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.
*
* GSN 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 GSN. If not, see <http://www.gnu.org/licenses/>.
*
* File: src/ch/epfl/gsn/storage/hibernate/HibernateStorage.java
*
* @author Timotee Maret
*
*/
package ch.epfl.gsn.storage.hibernate;
import org.slf4j.LoggerFactory;
import ch.epfl.gsn.beans.DataField;
import ch.epfl.gsn.beans.DataTypes;
import ch.epfl.gsn.beans.StreamElement;
import ch.epfl.gsn.storage.DataEnumeratorIF;
import ch.epfl.gsn.utils.GSNRuntimeException;
import org.slf4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import java.io.Serializable;
import java.util.*;
public class HibernateStorage implements VirtualSensorStorage {
private static final transient Logger logger = LoggerFactory.getLogger(HibernateStorage.class);
private SessionFactory sf;
private String identifier;
private DataField[] structure;
private static final int PAGE_SIZE = 1000;
public static HibernateStorage newInstance(DBConnectionInfo dbInfo, String identifier, DataField[] structure, boolean unique) {
try {
return new HibernateStorage(dbInfo, identifier, structure, unique);
}
catch (RuntimeException e) {
logger.error(e.getMessage());
return null;
}
}
private HibernateStorage(DBConnectionInfo dbInfo, String identifier, DataField[] structure, boolean unique) throws RuntimeException {
String em = generateEntityMapping(identifier, structure, unique);
this.sf = HibernateUtil.getSessionFactory(dbInfo.getDriverClass(), dbInfo.getUrl(), dbInfo.getUserName(), dbInfo.getPassword(), em);
if (this.sf == null)
throw new RuntimeException("Unable to instanciate the Storage for:" + identifier);
this.identifier = identifier.toLowerCase();
this.structure = structure;
}
public boolean init() {
return true;
}
public Serializable saveStreamElement(StreamElement se) throws GSNRuntimeException {
// Create the dynamic map
try {
return storeElement(se2dm(se));
}
catch (org.hibernate.exception.ConstraintViolationException e) {
StringBuilder sb = new StringBuilder();
sb.append("Error occurred on inserting data to the database, an stream element dropped due to: ")
.append(e.getMessage())
.append(". (Stream element: ")
.append(se.toString())
.append(")");
logger.warn(sb.toString());
throw new GSNRuntimeException(e.getMessage());
}
catch (RuntimeException e) {
throw new GSNRuntimeException(e.getMessage());
}
}
public StreamElement getStreamElement(Serializable pk) throws GSNRuntimeException {
Transaction tx = null;
try {
Session session = sf.getCurrentSession();
tx = session.beginTransaction();
Map<String, Serializable> dm = (Map<String, Serializable>) session.get(identifier, pk);
tx.commit();
return dm2se(dm);
} catch (RuntimeException e) {
try {
if (tx != null)
tx.rollback();
} catch (RuntimeException ex) {
logger.error("Couldn't roll back transaction.");
}
throw e;
}
}
public long countStreamElement() throws GSNRuntimeException {
Transaction tx = null;
try {
Session session = sf.getCurrentSession();
tx = session.beginTransaction();
Criteria criteria = session.createCriteria(identifier);
criteria.setProjection(Projections.rowCount());
List count = criteria.list();
tx.commit();
return (Long) count.get(0);
} catch (RuntimeException e) {
try {
if (tx != null)
tx.rollback();
} catch (RuntimeException ex) {
logger.error("Couldn't roll back transaction.");
}
throw e;
}
}
public DataEnumeratorIF getStreamElements(int pageSize, Order order, Criterion[] crits, int maxResults) throws GSNRuntimeException {
return new PaginatedDataEnumerator(pageSize, order, crits, maxResults);
}
public DataEnumeratorIF getStreamElements(int pageSize, Order order, Criterion[] crits) throws GSNRuntimeException {
return getStreamElements(pageSize, order, crits, -1);
}
//
private Serializable storeElement(Map dm) {
Transaction tx = null;
try {
Session session = sf.getCurrentSession();
tx = session.beginTransaction();
Serializable pk = session.save(identifier, dm);
tx.commit();
return pk;
} catch (RuntimeException e) {
try {
if (tx != null)
tx.rollback();
} catch (RuntimeException ex) {
logger.error("Couldn't roll back transaction.");
}
throw e;
}
}
protected void finalize() throws Throwable {
try {
if (sf != null)
HibernateUtil.closeSessionFactory(sf);
} finally {
super.finalize();
}
}
//
/**
* @param gsnType
* @return
*/
public static String convertGSNTypeToLocalType(DataField gsnType) {
switch (gsnType.getDataTypeID()) {
case DataTypes.VARCHAR:
case DataTypes.CHAR:
return "string";
case DataTypes.BIGINT:
case DataTypes.TIME:
return "long";
case DataTypes.INTEGER:
return "integer";
case DataTypes.SMALLINT:
return "short";
case DataTypes.TINYINT:
return "byte";
case DataTypes.DOUBLE:
return "double";
case DataTypes.FLOAT:
return "float";
case DataTypes.BINARY:
return "binary";
}
return null;
}
private StreamElement dm2se(Map<String, Serializable> dm) {
ArrayList<Serializable> data = new ArrayList<Serializable>();
long timed = (Long) dm.get("timed");
for (DataField df : structure) {
if (!"timed".equalsIgnoreCase(df.getName()))
data.add(dm.get(df.getName()));
}
return new StreamElement(structure, data.toArray(new Serializable[]{data.size()}), timed);
}
private Map<String, Serializable> se2dm(StreamElement se) {
Map<String, Serializable> dm = new HashMap<String, Serializable>();
dm.put("timed", se.getTimeStamp());
for (String fieldName : se.getFieldNames()) {
if (!"timed".equalsIgnoreCase(fieldName))
dm.put(fieldName, se.getData(fieldName));
}
return dm;
}
/**
* Create the Hibernate mapping configuration file for the specified virtual sensor, according to the structure.
* The <code>pk</code> and <code>timed</code> are added by default to the mapping. Moreover, an index on
* the <code>timed</code> field is created. Finally, an optional <code>UNIQUE</code> clause is added to the
* <code>timed</code> column, iff the parameter <code>unique</code> is set to <code>true</code>.
*
* @param identifier
* @param structure
* @param unique
* @return return a StringBuilder containing the hibernate mapping configuration
*/
private static String generateEntityMapping(String identifier, DataField[] structure, boolean unique) {
StringBuilder sb = new StringBuilder();
sb.append("<!DOCTYPE hibernate-mapping PUBLIC\n");
sb.append("\"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"\n");
sb.append("\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">\n");
sb.append("<hibernate-mapping>\n");
sb.append("<class entity-name=\"")
.append(identifier.toLowerCase())
.append("\" table=\"")
.append(identifier.toLowerCase())
.append("\">\n");
//sb.append("<cache usage=\"read-only\"/>");
// PK field
sb.append("<id type=\"long\" column=\"PK\" name=\"pk\" >\n");
sb.append("<generator class=\"native\"/>\n");
sb.append("</id>\n");
// TIMED field and it index
sb.append("<property name=\"timed\" column=\"TIMED\" type=\"long\"");
sb.append(" index=\"")
.append(identifier.toUpperCase())
.append("_TIMED_INDEX\"");
sb.append(" not-null=\"true\"");
if (unique) {
sb.append(" unique=\"true\"");
}
sb.append(" />\n");
// OTHER DATA FIELDS
for (DataField df : structure) {
if (!"timed".equalsIgnoreCase(df.getName())) {
sb.append("<property name=\"")
.append(df.getName())
.append("\" column=\"")
.append(df.getName().toUpperCase())
.append("\" type=\"")
.append(convertGSNTypeToLocalType(df))
.append("\"/>\n");
}
}
sb.append("</class>\n");
sb.append("</hibernate-mapping>\n");
return sb.toString();
}
//
private class PaginatedDataEnumerator implements DataEnumeratorIF {
/** The global max number of result returned */
private int maxResults;
private int currentPage;
private int pageSize;
private Order order;
private Criterion[] crits;
private Iterator<Map<String, Serializable>> pci;
private boolean closed;
private PaginatedDataEnumerator(int pageSize, Order order, Criterion[] crits, int maxResults) {
this.maxResults = maxResults;
this.pageSize = pageSize;
this.order = order;
this.crits = crits;
currentPage = 0;
pci = null; //page content iterator
if (maxResults == 0)
close();
hasMoreElements();
}
/**
* This method checks if there is one or more {@link ch.epfl.gsn.beans.StreamElement} available in the DataEnumerator.
* If the current page is empty, it tries to load the next page.
* @return
*/
public boolean hasMoreElements() {
// Check if the DataEnumerator is closed
if (closed)
return false;
// Check if there is still data in the current pageContent
if (pci != null && pci.hasNext())
return true;
// Compute the next number of elements to fetch
int offset = currentPage * pageSize;
int mr = pageSize;
if (maxResults > 0) {
int remaining = maxResults - offset;
mr = remaining > 0 ? remaining >= pageSize ? pageSize : remaining % pageSize : 0;
}
// Try to load the next page
pci = null;
Transaction tx = null;
try {
Session session = sf.getCurrentSession();
tx = session.beginTransaction();
//
Criteria criteria = session.createCriteria(identifier);
for (Criterion criterion : crits) {
criteria.add(criterion);
}
criteria.addOrder(order);
criteria.setCacheable(true);
criteria.setReadOnly(true);
criteria.setFirstResult(offset);
criteria.setMaxResults(mr);
//
pci = criteria.list().iterator();
tx.commit();
currentPage++;
} catch (RuntimeException e) {
try {
if (tx != null)
tx.rollback();
} catch (RuntimeException ex) {
logger.error("Couldn't roll back transaction.");
}
throw e;
}
if(pci != null && pci.hasNext()) {
return true;
}
else {
close();
return false;
}
}
public StreamElement nextElement() throws RuntimeException {
if ( ! hasMoreElements())
throw new IndexOutOfBoundsException("The DataEnumerator has no more StreamElement or is closed.");
else
return dm2se(pci.next());
}
public void close() {
if (! closed)
closed = true;
}
}
}