/* Copyright (c) 2011 Danish Maritime Authority
*
* This library 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.
*
* This library 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 General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package dk.dma.ais.abnormal.event.db.jpa;
import com.google.inject.Inject;
import dk.dma.ais.abnormal.event.db.EventRepository;
import dk.dma.ais.abnormal.event.db.domain.Event;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import static java.lang.System.currentTimeMillis;
/**
* JpaEventRepository is an implementation of the EventRepository interface which
* manages persistent Event objects in a relational database accessed via Hibernate.
*/
@SuppressWarnings("JpaQlInspection")
public class JpaEventRepository implements EventRepository {
private static final Logger LOG = LoggerFactory.getLogger(JpaEventRepository.class);
{
LOG.info(this.getClass().getSimpleName() + " created (" + this + ").");
}
private final SessionFactory sessionFactory;
private final boolean readonly;
@Inject
public JpaEventRepository(SessionFactory sessionFactory, boolean readonly) {
this.readonly = readonly;
this.sessionFactory = sessionFactory;
}
private Session getSession() {
Session session = sessionFactory.openSession();
LOG.debug("Database session acquired: " + session);
if (readonly) {
session.setDefaultReadOnly(true);
}
return session;
}
@Override
public List<String> getEventTypes() {
Session session = getSession();
List events = null;
try {
Query query = session.createQuery("SELECT DISTINCT e.class AS c FROM Event e WHERE e.suppressed=false ORDER BY c");
events = query.list();
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
return events;
}
@Override
public void save(Event event) {
Session session = getSession();
try {
session.beginTransaction();
session.saveOrUpdate(event);
session.getTransaction().commit();
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
}
@Override
public Event getEvent(long eventId) {
Event event;
Session session = getSession();
try {
event = (Event) session.get(Event.class, eventId);
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
return event;
}
@Override
public List<Event> findEventsByFromAndToAndTypeAndVesselAndArea(Date from, Date to, String type, String vessel, Double north, Double east, Double south, Double west) {
Session session = getSession();
boolean usesFrom = false, usesTo = false, usesType = false, usesVessel = false, usesArea = false;
List events = null;
try {
StringBuilder hql = new StringBuilder();
hql.append("SELECT DISTINCT e FROM Event e ");
if (! StringUtils.isBlank(vessel)) {
hql.append("LEFT JOIN e.behaviours AS b ");
}
if (north != null && east != null && south != null && west != null) {
hql.append("LEFT JOIN e.behaviours AS b LEFT JOIN b.trackingPoints AS tp WHERE e.suppressed=false AND latitude<:north AND latitude>:south AND longitude<:east AND longitude>:west AND ");
usesArea = true;
} else {
hql.append("WHERE e.suppressed=false AND ");
}
// from
if (from != null) {
hql.append("(e.startTime >= :from OR e.endTime >= :from) AND ");
usesFrom = true;
}
// to
if (to != null) {
hql.append("(e.startTime <= :to OR e.endTime <= :to) AND ");
usesTo = true;
}
// type
if (! StringUtils.isBlank(type)) {
hql.append("TYPE(e) IN (:classes) AND ");
usesType = true;
}
// vessel
if (! StringUtils.isBlank(vessel)) {
hql.append("(");
hql.append("b.vessel.callsign LIKE :vessel OR ");
hql.append("b.vessel.name LIKE :vessel OR ");
try {
Long vesselAsLong = Long.valueOf(vessel);
hql.append("b.vessel.mmsi = :vessel OR ");
hql.append("b.vessel.imo = :vessel OR ");
} catch (NumberFormatException e) {
}
hql.replace(hql.length()-3, hql.length(), ")"); // "OR " -> ")"
if (! vessel.startsWith("%")) {
vessel = "%" + vessel;
}
if (! vessel.endsWith("%")) {
vessel = vessel + "%";
}
usesVessel = true;
}
//
String hqlAsString = hql.toString().trim();
if (hqlAsString.endsWith("AND")) {
hqlAsString = hqlAsString.substring(0, hqlAsString.lastIndexOf("AND"));
}
//
Query query = session.createQuery(hqlAsString);
if (usesArea) {
query.setParameter("north", north);
query.setParameter("east", east);
query.setParameter("south", south);
query.setParameter("west", west);
}
if (usesFrom) {
query.setParameter("from", from);
}
if (usesTo) {
query.setParameter("to", to);
}
if (usesType) {
String className = "dk.dma.ais.abnormal.event.db.domain." + type;
try {
Class clazz = Class.forName(className);
query.setParameter("classes", clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class " + className + " not found.");
}
}
if (usesVessel) {
query.setParameter("vessel", vessel);
}
LOG.debug("Query: " + query.toString());
final long t0 = currentTimeMillis();
events = query.list();
final long t1 = currentTimeMillis();
LOG.debug("Found " + events.size() + " matching events in " + (t1-t0) + " msecs.");
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
return events;
}
@Override
public List<Event> findEventsByFromAndTo(Date from, Date to) {
LOG.debug("Searching events from " + from + " to " + to + ".");
Session session = getSession();
List events = null;
try {
StringBuilder hql = new StringBuilder();
hql.append("SELECT e FROM Event e WHERE ");
hql.append("(e.suppressed=false AND e.startTime >= :from AND e.startTime <= :to) OR ");
hql.append("(e.suppressed=false AND e.endTime >= :from AND e.endTime <= :to) ");
hql.append("ORDER BY e.startTime");
Query query = session.createQuery(hql.toString());
query.setParameter("from", from);
query.setParameter("to", to);
events = query.list();
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
LOG.debug("Found " + events.size() + " matching events.");
return events;
}
@Override
public List<Event> findRecentEvents(int numberOfEvents) {
Session session = getSession();
List events = null;
try {
Query query = session.createQuery("SELECT e FROM Event e WHERE e.suppressed=false ORDER BY e.startTime DESC");
query.setMaxResults(numberOfEvents);
events = query.list();
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
return events;
}
@Override
public <T extends Event> T findOngoingEventByVessel(int mmsi, Class<T> eventClass) {
Session session = getSession();
T event = null;
try {
Query query = session.createQuery("SELECT e FROM Event e LEFT JOIN e.behaviours b WHERE TYPE(e) = :clazz AND e.state = :state AND b.vessel.mmsi = :mmsi AND e.suppressed=false");
query.setCacheable(true);
query.setParameter("clazz", eventClass);
query.setString("state", "ONGOING");
query.setInteger("mmsi", mmsi);
List events = query.list();
if (events.size() > 0) {
if (events.size() > 1) {
LOG.warn("More than one (" + events.size() + ") ongoing event of type " + eventClass + "; expected max. 1. Using first.");
}
event = (T) events.get(0);
}
} finally {
session.close();
LOG.debug("Database session closed: " + session);
}
return event;
}
}