/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.monitor.hib; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; import org.geoserver.monitor.CompositeFilter; import org.geoserver.monitor.Filter; import org.geoserver.monitor.FilterVisitorSupport; import org.geoserver.monitor.MonitorConfig; import org.geoserver.monitor.MonitorConfig.Mode; import org.geoserver.monitor.MonitorDAO; import org.geoserver.monitor.PipeliningTaskQueue; import org.geoserver.monitor.Query; import org.geoserver.monitor.Query.Comparison; import org.geoserver.monitor.Query.SortOrder; import org.geoserver.monitor.RequestData; import org.geoserver.monitor.RequestDataVisitor; import org.geoserver.ows.util.OwsUtils; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.springframework.beans.factory.DisposableBean; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; import com.google.common.base.FinalizableReference; public class HibernateMonitorDAO2 implements MonitorDAO , DisposableBean { public static enum Sync { SYNC, ASYNC, ASYNC_UPDATE; } HibernateTemplate hib; PipeliningTaskQueue<Thread> tasks; Mode mode = Mode.HISTORY; Sync sync = Sync.ASYNC; public HibernateMonitorDAO2() { setMode(Mode.HISTORY); setSync(Sync.ASYNC); } public String getName() { return "hibernate"; } @Override public void init(MonitorConfig config) { setMode(config.getMode()); setSync(getSync(config)); } public Sync getSync(MonitorConfig config) { return Sync.valueOf(config.getProperties().getProperty("hibernate.sync", "async").toUpperCase()); } public void setSync(Sync sync) { this.sync = sync; if (sync != Sync.SYNC) { if (tasks == null) { tasks = new PipeliningTaskQueue<Thread>(); tasks.start(); } } else { if (tasks != null) { dispose(); } } } public void setMode(Mode mode) { this.mode = mode; } public void setSessionFactory(SessionFactory sessionFactory) { hib = new HibernateTemplate(sessionFactory); hib.setFetchSize(1000); } public SessionFactory getSessionFactory() { return hib.getSessionFactory(); } public RequestData init(final RequestData data) { if (mode != Mode.HISTORY) { if (sync == Sync.ASYNC_UPDATE) { //async_update means don't run the initial insert asynchronously new Insert(data).run(); } else { run(new Insert(data)); } } else { //don't persist yet, we persist at the very end of request } return data; } public void add(RequestData data) { if (sync == Sync.ASYNC_UPDATE) { //async_update means don't run the initial insert asynchronously new Insert(data).run(); } else { run(new Insert(data)); } } public void update(RequestData data) { save(data); } public void save(RequestData data) { run(new Save(data)); // if(data.getId() == -1) { // run(new Insert(data)); // } // else { // run(new Update(data)); // } } public void clear() { } public void dispose() { if (tasks != null) { tasks.shutdown(); tasks = null; } } public List<RequestData> getOwsRequests() { throw new UnsupportedOperationException(); } public List<RequestData> getOwsRequests(String service, String operation, String version) { throw new UnsupportedOperationException(); } public RequestData getRequest(long id) { return (RequestData) hib.get(RequestData.class, id); } @SuppressWarnings("unchecked") public List<RequestData> getRequests() { return all(RequestData.class, "startTime"); } @SuppressWarnings("unchecked") public List<RequestData> getRequests(Query q) { return query(q); } public void getRequests(Query q, RequestDataVisitor visitor) { query(q, visitor); } public long getCount(Query q) { q = q.clone(); q.getAggregates().clear(); q.getProperties().clear(); q.getGroupBy().clear(); q.setSortBy(null); q.setSortOrder(null); q.aggregate("count()"); org.hibernate.Query query = toQuery(q); long count = ((Number)query.uniqueResult()).longValue(); // factor in offset, count if (q.getOffset() != null) { count = Math.max(0, count - q.getOffset()); } if (q.getCount() != null) { count = Math.min(count, q.getCount()); } return count; } public Iterator<RequestData> getIterator(Query q) { org.hibernate.Query query = toQuery(q); return new RequestDataIterator(query.iterate(), q); } class RequestDataIterator implements Iterator<RequestData> { Iterator it; Query query; public RequestDataIterator(Iterator it, Query query) { this.it = it; this.query = query; } public boolean hasNext() { return it.hasNext(); } public RequestData next() { return toRequest(it.next(), query); } public void remove() { // TODO Auto-generated method stub } } // public ResourceData getLayer(String name) { // return (ResourceData) hib.get(ResourceData.class, name); // } // // public List<ResourceData> getLayers() { // return all(ResourceData.class, null); // } // // public List<ResourceData> getLayers(MonitorQuery q) { // return query(q, ResourceData.class); // } // // public void getLayers(MonitorQuery q, MonitorVisitor<ResourceData> visitor) { // query(q, visitor, ResourceData.class); // } @SuppressWarnings("unchecked") protected List<RequestData> query(final Query q) { //TODO: handle the case of when the user specifies properties, return hib.execute(new HibernateCallback<List<RequestData>>() { public List<RequestData> doInHibernate(Session session) throws HibernateException, SQLException { List objs = new ArrayList<>(); org.hibernate.Query query = toQuery(q, objs); List list = query.list(); if (q.getProperties().isEmpty()) { //selected whole object, just return the list directly return list; } List<RequestData> results = new ArrayList<>(list.size()); Iterator it = list.iterator(); while(it.hasNext()) { Object next = it.next(); results.add(toRequest(next, q)); } return results; } }); } protected <T> void query(Query q, RequestDataVisitor visitor) { List<Object> objs = new ArrayList(); org.hibernate.Query query = toQuery(q, objs); Iterator it = query.iterate(); try { if (q.getProperties().isEmpty() && q.getAggregates().isEmpty()) { while(it.hasNext()) { visitor.visit((RequestData)it.next()); } } else { while(it.hasNext()) { //TODO: avoid the intense object creation, and reflection... we probably // just want to expose the values directly to the visitor int nprops = q.getProperties().size() + q.getAggregates().size(); Object[] values = nprops == 1 ? new Object[]{it.next()} : (Object[]) it.next(); RequestData data = toRequest(values, q); // aggregate properties Object[] aggregates = !q.getAggregates().isEmpty() ? new Object[q.getAggregates().size()] : null; int off = q.getProperties().size(); for (int i = 0; i < q.getAggregates().size(); i++) { aggregates[i] = values[off+i]; } visitor.visit(data, aggregates); } } } finally { hib.closeIterator(it); } } protected RequestData toRequest(Object obj, Query q) { if (obj instanceof RequestData) { return (RequestData) obj; } Object[] values = obj instanceof Object[] ? (Object[]) obj : new Object[]{obj}; RequestData data = null; try { data = RequestData.class.newInstance(); } catch(Exception e ) {}; //regular properties for (int i = 0; i < q.getProperties().size(); i++) { String prop = q.getProperties().get(i); if (prop.equals("resource")) { //this means a joined query in which they selected an individual // accesses resource from the RequestData.resources collection data.getResources().add((String) values[i]); } else { OwsUtils.set(data, prop, values[i]); } } return data; } @SuppressWarnings("unchecked") protected <T> List<T> all(final Class<T> clazz, final String orderBy) { return hib.execute(new HibernateCallback<List<T>>() { public List<T> doInHibernate(Session session) throws HibernateException, SQLException { StringBuffer sb = new StringBuffer(); sb.append("SELECT x "); sb.append("FROM ").append(clazz.getSimpleName()).append(" x "); if (orderBy != null) { sb.append("ORDER BY x.").append(orderBy).append(" DESC"); } org.hibernate.Query q = session.createQuery(sb.toString()); return q.list(); } }); } protected org.hibernate.Query toQuery(Query q) { return toQuery(q, new ArrayList()); } protected org.hibernate.Query toQuery(Query q, List<Object> objs) { String hql = toHQL(q, objs); org.hibernate.Query query = hib.getSessionFactory().getCurrentSession().createQuery(hql); if (q.getOffset() != null) { query.setFirstResult(q.getOffset().intValue()); } if (q.getCount() != null) { query.setMaxResults(q.getCount().intValue()); } for (int i = 0; i < objs.size(); i++) { query.setParameter(i, objs.get(i)); } return query; } protected String toHQL(Query q, List<Object> objs) { String entity = RequestData.class.getSimpleName(); String prefix = Character.toLowerCase(entity.charAt(0)) + "d"; boolean join = false; StringBuffer sb = new StringBuffer("SELECT "); if (q.getProperties().isEmpty() && q.getAggregates().isEmpty()) { //select the whole object sb.append(prefix); } else { //only load the specified properties for (String prop : q.getProperties()) { prefix(prop, prefix, sb).append(", "); if (prop.equals("resource")) { join = true; } } for (String agg : q.getAggregates()) { //handle aggregate functions if (agg.equals("count()")) { agg = "count(" + prefix + ")"; } sb.append(agg).append(", "); } sb.setLength(sb.length()-2); } sb.append(" FROM ").append(entity).append(" ").append(prefix); if (!join && q.getFilter() != null) { //we may need to join depending on what is found in the query JoinDeterminer jt = new JoinDeterminer(); q.getFilter().accept(jt); join = jt.doJoin(); } if (join) { sb.append(" LEFT JOIN ").append(prefix).append(".resources AS resource"); } if (q.getFilter() != null) { sb.append(" WHERE "); q.getFilter().accept(new FilterEncoder(sb, prefix, objs)); } if (q.getFromDate() != null || q.getToDate() != null) { if (q.getFilter() != null) { sb.append(" AND"); } else { sb.append(" WHERE"); } sb.append(" ").append(prefix).append(".startTime"); if (q.getFromDate() != null && q.getToDate() != null) { sb.append(" BETWEEN ? AND ?"); objs.add(q.getFromDate()); objs.add(q.getToDate()); } else if (q.getFromDate() != null) { sb.append(" >= ?"); objs.add(q.getFromDate()); } else { sb.append(" <= ?"); objs.add(q.getToDate()); } } if (!q.getGroupBy().isEmpty()) { sb.append(" GROUP BY "); for (String prop : q.getGroupBy()) { prefix(prop, prefix, sb).append(","); } sb.setLength(sb.length()-1); } if (q.getSortBy() != null) { sb.append(" ORDER BY "); String sortBy = q.getSortBy(); if (sortBy.equals("count()")) { sb.append("count(" + prefix + ")"); } else { prefix(sortBy, prefix, sb); } sb.append(" ").append(q.getSortOrder()); } else if (q.getFromDate() != null || q.getToDate() != null) { //only sort if this is not an aggregate query if (q.getAggregates().isEmpty()) { //by default sort dates descending sb.append(" ORDER BY ").append(prefix).append(".startTime").append(" ").append(SortOrder.DESC); } } return sb.toString(); } StringBuffer prefix(String prop, String prefix, StringBuffer sb) { //TODO: hack, resource is a special property here... figure out something better if (!"resource".equals(prop)) { sb.append(prefix).append("."); } sb.append(prop); return sb; } static class JoinDeterminer extends FilterVisitorSupport { boolean join = false; @Override protected void handleFilter(Filter f) { if ("resource".equals(f.getLeft())) { join = true; } } public boolean doJoin() { return join; } } class FilterEncoder extends FilterVisitorSupport { StringBuffer sb; String prefix; List objs; FilterEncoder(StringBuffer sb, String prefix, List objs) { this.sb = sb; this.prefix = prefix; this.objs = objs; } protected void handleComposite(CompositeFilter f, String type) { sb.append("("); for (Filter g : f.getFilters()) { visit(g); sb.append(" ").append(type).append(" "); } sb.setLength(sb.length()-(type.length()+2)); sb.append(")"); } protected void handleFilter(Filter f) { if (isProperty(f.getLeft())) { prefix((String)f.getLeft(), prefix, sb); } else { sb.append(" ?"); objs.add(f.getLeft()); } if (f.getRight() == null) { sb.append(" IS"); if (f.getType() != Comparison.EQ) { sb.append( " NOT"); } sb.append(" NULL"); } else { sb.append(" ").append(f.getType()); if (f.getType() == Comparison.IN) { if (isProperty(f.getRight())) { sb.append(" elements(").append(f.getRight()).append(")"); } else { sb.append(" ("); for (Object o : (List)f.getRight()) { sb.append("?, "); objs.add(o); } sb.setLength(sb.length()-2); sb.append(")"); } } else { if (isProperty(f.getRight())) { prefix((String)f.getRight(), prefix, sb); } else { sb.append(" ?"); objs.add(f.getRight()); } } } } boolean isProperty(Object obj) { if (obj instanceof String) { String s = (String) obj; return "resource".equals(s) || OwsUtils.has(new RequestData(), s); } return false; } } // protected void mergeLayers(RequestData data, Session session) { // for (int i = 0; i < data.getLayers().size(); i++) { // ResourceData l = data.getLayers().get(i); // l = (ResourceData) session.merge(l); // data.getLayers().set(i, l); // } // } protected void run(Task task) { if (tasks != null) { tasks.execute(Thread.currentThread(), new Async(task), task.desc); } else { task.run(); } } class Async implements Runnable { Task task; Async(Task task) { this.task = task; } public void run() { synchronized(task.data) { task.run(); } } } static abstract class Task implements Runnable { RequestData data; String desc; Task(RequestData data) { this.data = data; } } class Save extends Task { RequestData data; Save(RequestData data) { super(data); this.data = data; } public void run() { if (data.getId() == -1) { new Insert(data).run(); } else { new Update(data).run(); } } } class Insert extends Task { Insert(RequestData data) { super(data); this.desc = "Insert " + data.internalid; } public void run() { hib.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Transaction tx = session.beginTransaction(); data.setId((Long)session.save(data)); //mergeLayers(data, session); session.save(data); tx.commit(); return data; } }); } } class Update extends Task { Update(RequestData data) { super(data); this.desc = "Update " + data.internalid; } public void run() { hib.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { try { Transaction tx = session.beginTransaction(); //mergeLayers(data, session); session.update(data); tx.commit(); } catch(HibernateException e) { if (tasks != null) tasks.print(); throw e; } return null; } }); } } @Override public void destroy() throws Exception { getSessionFactory().close(); } }