/* * Copyright 2006-2015 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.logic; import java.sql.SQLException; import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import ome.annotations.RolesAllowed; import ome.api.IQuery; import ome.api.ServiceInterface; import ome.api.local.LocalQuery; import ome.conditions.ApiUsageException; import ome.conditions.ValidationException; import ome.model.IObject; import ome.parameters.Filter; import ome.parameters.Parameters; import ome.services.SearchBean; import ome.services.query.Query; import ome.services.search.FullText; import ome.services.search.SearchValues; import ome.tools.hibernate.QueryBuilder; import org.apache.lucene.analysis.Analyzer; import org.hibernate.Criteria; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.NonUniqueResultException; import org.hibernate.ObjectNotFoundException; import org.hibernate.Session; import org.hibernate.criterion.Example; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; import org.hibernate.metadata.ClassMetadata; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * Provides methods for directly querying object graphs. * * @author Josh Moore      <a * href="mailto:josh.moore@gmx.de">josh.moore@gmx.de</a> * @version 3.0 <small> (<b>Internal version:</b> $Rev$ $Date$) </small> * @since 3.0 * */ @Transactional(readOnly = true) public class QueryImpl extends AbstractLevel1Service implements LocalQuery { protected Class<? extends Analyzer> analyzer; public void setAnalyzer(Class<? extends Analyzer> analyzer) { this.analyzer = analyzer; } public Class<? extends ServiceInterface> getServiceInterface() { return IQuery.class; } /** * Temporary WORKAROUND during the removal of HibernateTemplate to * let IQuery continue functioning. */ private HibernateTemplate getHibernateTemplate() { return new HibernateTemplate(getSessionFactory(), false); } // ~ LOCAL PUBLIC METHODS // ========================================================================= @RolesAllowed("user") @Transactional(readOnly = false) public boolean contains(Object obj) { return getHibernateTemplate().contains(obj); } @RolesAllowed("user") @Transactional(readOnly = false) public void evict(Object obj) { getHibernateTemplate().evict(obj); } @RolesAllowed("user") @Transactional(readOnly = false) public void clear() { getHibernateTemplate().clear(); } @RolesAllowed("user") public void initialize(Object obj) { Hibernate.initialize(obj); } @RolesAllowed("user") @Transactional(propagation = Propagation.SUPPORTS) public boolean checkType(String type) { ClassMetadata meta = getHibernateTemplate().getSessionFactory() .getClassMetadata(type); return meta == null ? false : true; } @RolesAllowed("user") @Transactional(propagation = Propagation.SUPPORTS) public boolean checkProperty(String type, String property) { ClassMetadata meta = getHibernateTemplate().getSessionFactory() .getClassMetadata(type); String[] names = meta.getPropertyNames(); for (int i = 0; i < names.length; i++) { // TODO: possibly with caching and Arrays.sort/search if (names[i].equals(property)) { return true; } } return false; } /** * @see LocalQuery#execute(HibernateCallback) */ @RolesAllowed("user") @SuppressWarnings("unchecked") public <T> T execute(HibernateCallback callback) { return (T) getHibernateTemplate().execute(callback); } /** * @see ome.api.local.LocalQuery#execute(Query) */ @RolesAllowed("user") @SuppressWarnings("unchecked") public <T> T execute(ome.services.query.Query<T> query) { return (T) getHibernateTemplate().execute(query); } // ~ INTERFACE METHODS // ========================================================================= @Override @RolesAllowed("user") @SuppressWarnings("unchecked") // TODO weirdness here; learn more about CGLIB initialization. public IObject get(final Class klass, final long id) throws ValidationException { return (IObject) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { IObject o = null; try { o = (IObject) session.load(klass, id); } catch (ObjectNotFoundException onfe) { throw new ApiUsageException( String .format( "The requested object (%s,%s) is not available.\n" + "Please use IQuery.find to determine existence.\n", klass.getName(), id)); } Hibernate.initialize(o); return o; } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") // TODO weirdness here; learn more about CGLIB initialization. public IObject find(final Class klass, final long id) { return (IObject) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { IObject o = (IObject) session.get(klass, id); Hibernate.initialize(o); return o; } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> List<T> findAll(final Class<T> klass, final Filter filter) { if (filter == null) { return getHibernateTemplate().loadAll(klass); } return (List<T>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Criteria c = session.createCriteria(klass); c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); parseFilter(c, filter); return c.list(); } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> T findByExample(final T example) throws ApiUsageException { return (T) getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { try { Criteria c = session.createCriteria(example.getClass()); c.add(Example.create(example)); return c.uniqueResult(); } catch (IncorrectResultSizeDataAccessException irsdae) { throwNonUnique("findByExample"); } catch (NonUniqueResultException nure) { throwNonUnique("findByExample"); } // Never reached! return null; } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> List<T> findAllByExample(final T example, final Filter filter) { return (List<T>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Criteria c = session.createCriteria(example.getClass()); c.add(Example.create(example)); parseFilter(c, filter); return c.list(); } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> T findByString(final Class<T> klass, final String fieldName, final String value) throws ApiUsageException { return (T) getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { try { Criteria c = session.createCriteria(klass); c.add(Restrictions.eq(fieldName, value)); return c.uniqueResult(); } catch (IncorrectResultSizeDataAccessException irsdae) { throwNonUnique("findByString"); } catch (NonUniqueResultException nure) { throwNonUnique("findByString"); } // Never reached return null; } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> List<T> findAllByString(final Class<T> klass, final String fieldName, final String value, final boolean caseSensitive, final Filter filter) throws ApiUsageException { return (List<T>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Criteria c = session.createCriteria(klass); parseFilter(c, filter); if (caseSensitive) { c.add(Restrictions.like(fieldName, value, MatchMode.ANYWHERE)); } else { c.add(Restrictions.ilike(fieldName, value, MatchMode.ANYWHERE)); } return c.list(); } }); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> T findByQuery(String queryName, Parameters params) throws ValidationException { if (params == null) { params = new Parameters(); } // specify that we should only return a single value if possible params.unique(); Query<T> q = getQueryFactory().lookup(queryName, params); T result = null; try { result = execute(q); } catch (ClassCastException cce) { throw new ApiUsageException( "Query named:\n\t" + queryName + "\n" + "has returned an Object of type " + cce.getMessage() + "\n" + "Queries must return IObjects when using findByQuery. \n" + "Please try findAllByQuery for queries which return Lists. "); } catch (IncorrectResultSizeDataAccessException irsdae) { throwNonUnique(queryName); } catch (NonUniqueResultException nure) { throwNonUnique(queryName); } return result; } private void throwNonUnique(String queryName) { throw new ApiUsageException( "Query named:\n\n\t" + queryName + "\n\n" + "has returned more than one Object\n" + "findBy methods must return a single value.\n" + "Please try findAllBy methods for queries which return Lists."); } @Override @RolesAllowed("user") public <T extends IObject> List<T> findAllByQuery(String queryName, Parameters params) { Query<List<T>> q = getQueryFactory().lookup(queryName, params); return execute(q); } @Override @RolesAllowed("user") @SuppressWarnings("unchecked") public <T extends IObject> List<T> findAllByFullText(final Class<T> type, final String query, final Parameters params) { if (analyzer == null) { throw new ApiUsageException( "IQuery not configured for full text search.\n" + "Please use ome.api.Search instead."); } List<IObject> results = (List<IObject>) getHibernateTemplate().execute( new HibernateCallback<List<IObject>>() { @SuppressWarnings("rawtypes") public List<IObject> doInHibernate(Session session) throws HibernateException, SQLException { SearchValues values = new SearchValues(); values.onlyTypes = Arrays.asList((Class) type); values.copy(params); FullText fullText = new FullText(values, query, analyzer); return (List<IObject>) fullText.doWork(session, null); } }); if (results == null || results.size() == 0) { return new ArrayList<T>(); } SearchBean search = new SearchBean(); search.addParameters(params); search.addResult(results); return search.results(); } // ~ Aggregations // ========================================================================= @Override @SuppressWarnings("unchecked") @RolesAllowed("user") public List<Object[]> projection(final String query, Parameters p) { final Parameters params = (p == null ? new Parameters() : p); final Query<List<Object>> q = getQueryFactory().lookup(query, params); @SuppressWarnings("rawtypes") final List rv = (List) getHibernateTemplate().execute(q); final int size = rv.size(); Object obj = null; for (int i = 0; i < size; i++) { obj = rv.get(i); if (obj != null) { if (!Object[].class.isAssignableFrom(obj.getClass())) { rv.set(i, new Object[]{obj}); } } } for (int i = 0; i < size; i++) { Object[] x = (Object[]) rv.get(i); if (x != null && x.length == 1 && x[0] instanceof Map) { Map<Object, Object> y = (Map<Object, Object>) x[0]; for (Map.Entry<Object, Object> z : y.entrySet()) { if (z != null && z.getKey().toString().endsWith("_details_permissions")) { z.setValue(new ome.util.PermDetails((IObject) z.getValue())); } } } } return rv; } final static Pattern AGGS = Pattern.compile("(count|sum|max|min)"); final static Pattern FIELD = Pattern.compile("\\w?[\\w.\\s\\+\\-\\*]*"); @RolesAllowed("user") public Long aggByQuery(String agg, String field, String query, Parameters params) { if (!AGGS.matcher(agg).matches()) { throw new ValidationException(agg + " does not match " + AGGS); } if (!FIELD.matcher(field).matches()) { throw new ValidationException(field + " does not match " + FIELD); } final QueryBuilder qb = new QueryBuilder(); qb.select(agg + "("+field+")").append(query); qb.params(params); return (Long) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { return qb.query(session).uniqueResult(); } }); } @SuppressWarnings("unchecked") @RolesAllowed("user") public Map<String, Long> aggMapByQuery(String agg, String mapKey, String field, String query, Parameters params) { if (!AGGS.matcher(agg).matches()) { throw new ValidationException(agg + " does not match " + AGGS); } if (!FIELD.matcher(field).matches()) { throw new ValidationException(field + " does not match " + FIELD); } if (!FIELD.matcher(mapKey).matches()) { throw new ValidationException(mapKey + " does not match " + FIELD); } final QueryBuilder qb = new QueryBuilder(); qb.select(mapKey, agg + "(" + field + ")").append(query); qb.append(" group by " + mapKey); qb.params(params); List<Object[]> list = (List<Object[]>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { return qb.query(session).list(); } }); Map<String, Long> rv = new HashMap<String, Long>(); for (Object[] objs : list) { Object key = objs[0]; Object value = objs[1]; Long l = null; if (value instanceof Long) { l = (Long) value; } else if (value instanceof Integer) { l = ((Integer) value).longValue(); } else { throw new ValidationException("Value for key " + key + " is " + value); } rv.put(key.toString(), l); } return rv; } // ~ Others // ========================================================================= @Override public <T extends IObject> T refresh(T iObject) throws ApiUsageException { getHibernateTemplate().refresh(iObject); return iObject; } // ~ HELPERS // ========================================================================= /** * Responsible for applying the information provided in a * {@link ome.parameters.Filter} to a {@link org.hibernate.Criteria} * instance. * @param c a criteria instance * @param f a filter to limit a query */ protected void parseFilter(Criteria c, Filter f) { // ticket:1232 - Reverting for 4.0 if (f != null && f.offset != null) { c.setFirstResult(f.offset); } else { c.setFirstResult(0); } if (f != null && f.limit != null) { c.setMaxResults(f.limit); } else { c.setMaxResults(Integer.MAX_VALUE); } } }