/* ############################################################################### # # # Copyright (C) 2011-2016 OpenMEAP, Inc. # # Credits to Jonathan Schang & Rob Thacher # # # # Released under the LGPLv3 # # # # OpenMEAP 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. # # # # OpenMEAP 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 Lesser General Public License # # along with OpenMEAP. If not, see <http://www.gnu.org/licenses/>. # # # ############################################################################### */ package com.openmeap.model; import java.sql.SQLException; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.PersistenceException; import javax.persistence.Query; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; import com.openmeap.event.ProcessingEvent; import com.openmeap.model.dto.Application; import com.openmeap.model.dto.ApplicationArchive; import com.openmeap.model.dto.ApplicationVersion; import com.openmeap.model.dto.Deployment; /** * Intended to basically pass-through to the entity manager. * * Implemented with the idea that a different persistence * mechanism might one day be used. * * @author schang */ @Transactional public class ModelServiceImpl implements ModelService { private Logger logger = LoggerFactory.getLogger(ModelServiceImpl.class); private EntityManager entityManager = null; /** * Number of times to retry refreshing a model entity before failing the operation. * Using SQLite, sometimes the database file is locked by another process. */ private int numberOfRefreshRetries=3; /** * Amount of time to wait between each refresh retry. */ private int refreshRetryInterval=250; public void clearPersistenceContext() { entityManager.clear(); } @Override public <T extends ModelEntity> T saveOrUpdate(T entity) throws PersistenceException { T entityToReturn = entity; try { // if we haven't loaded this object yet, // then attempt to do so if( ! entityManager.contains(entity) ) { entityToReturn = entityManager.merge(entity); } entityManager.persist(entityToReturn); } catch( PersistenceException pe ) { throw new PersistenceException(pe); } return entityToReturn; } @Override public <T extends ModelEntity> void delete(T obj2Delete) throws PersistenceException { _delete(obj2Delete,null); } @Override public <T extends ModelEntity> void refresh(T entity) throws PersistenceException { int numRetries = numberOfRefreshRetries; boolean notSuccessful = false; do { try{ if(entity!=null) { this._refresh(entity); } } catch(Exception e) { Throwable t = ExceptionUtils.getRootCause(e); if(!(t instanceof SQLException)) { throw new PersistenceException(e); } logger.warn("Unable to refresh model entity, "+numRetries+" left. "+t.getMessage()); numRetries--; notSuccessful=true; try { Thread.sleep(refreshRetryInterval); } catch (InterruptedException e1) { throw new PersistenceException("Thread sleep interrupted during the refresh retry interval: "+e1.getMessage(),e1); } } } while(notSuccessful && numRetries!=0); if(numRetries==0) { throw new PersistenceException("Unable to refresh model entity. "+numberOfRefreshRetries+" retries failed"); } } @SuppressWarnings("unchecked") public <T extends ModelEntity> T findByPrimaryKey(Class<T> clazz, Object pk) { try { T obj = (T)entityManager.find(clazz, pk); return obj; } catch( NoResultException nre ) { return null; } } @SuppressWarnings("unchecked") public <T extends ModelEntity> List<T> findAll(Class<T> clazz) { Query q = entityManager.createQuery("select distinct a from "+clazz.getCanonicalName()+" a"); try { return q.getResultList(); } catch( NoResultException nre ) { return null; } } @Override public Application findApplicationByName(String name) { Query q = entityManager.createQuery("select distinct a " +"from Application a " +"where a.name=:name"); q.setParameter("name", name); try { return (Application)q.getSingleResult(); } catch( NoResultException nre ) { return null; } } @Override public ApplicationVersion findAppVersionByNameAndId(String appName, String identifier) { Query q = entityManager.createQuery("select distinct av " +"from ApplicationVersion av " +"inner join fetch av.application a " +"where av.identifier=:identifier " +"and a.name=:name"); q.setParameter("name", appName); q.setParameter("identifier", identifier); try { ApplicationVersion ver = (ApplicationVersion)q.getSingleResult(); return ver; } catch( NoResultException nre ) { return null; } } @Override public List<Deployment> findDeploymentsByApplication(Application app) { Query q = entityManager.createQuery("select d " +"from Deployment d " +"inner join fetch d.applicationArchive aa " +"inner join d.application a " +"where a.name=:name"); q.setParameter("name", app.getName()); try { @SuppressWarnings(value={"unchecked"}) List<Deployment> deployments = (List<Deployment>)q.getResultList(); return deployments; } catch( NoResultException nre ) { return null; } } @Override public List<Deployment> findDeploymentsByApplicationArchive(ApplicationArchive archive) { Query q = entityManager.createQuery("select distinct d " +"from Deployment d " +"inner join fetch d.applicationArchive aa " +"where aa.id=:id" ); q.setParameter("id", archive.getId()); try { @SuppressWarnings(value={"unchecked"}) List<Deployment> deployments = (List<Deployment>)q.getResultList(); return deployments; } catch( NoResultException nre ) { return null; } } @Override public List<ApplicationVersion> findVersionsByApplicationArchive(ApplicationArchive archive) { Query q = entityManager.createQuery("select distinct av " +"from ApplicationVersion av " +"inner join fetch av.archive aa " +"where aa.id=:id " ); q.setParameter("id", archive.getId()); try { @SuppressWarnings(value={"unchecked"}) List<ApplicationVersion> versions = (List<ApplicationVersion>)q.getResultList(); return versions; } catch( NoResultException nre ) { return null; } } @Override public Deployment getLastDeployment(Application app) { Query q = entityManager.createQuery("select distinct d " +"from Deployment d join d.application " +"where d.application.id=:id " +"order by d.createDate desc"); q.setParameter("id", app.getId()); q.setMaxResults(1); try { Object o = q.getSingleResult(); return (Deployment)o; } catch( NoResultException nre ) { return null; } } @Override public ApplicationArchive getApplicationArchiveByDeployment(Deployment depl) { Query q = entityManager.createQuery("select distinct aa " +"from ApplicationArchive aa, Deployment d " +"where d.applicationArchive=aa and d.id=:id "); q.setParameter("id", depl.getId()); q.setMaxResults(1); try { Object o = q.getSingleResult(); return ((ApplicationArchive)o); } catch( NoResultException nre ) { return null; } } @Override public ApplicationArchive findApplicationArchiveByHashAndAlgorithm(Application app, String hash, String hashAlgorithm) { Query q = entityManager.createQuery("select distinct ar " +"from ApplicationArchive ar " +"join fetch ar.application app " +"where ar.hash=:hash " +"and ar.hashAlgorithm=:hashAlgorithm " +"and app.id=:appId"); q.setParameter("hash", hash); q.setParameter("hashAlgorithm", hashAlgorithm); q.setParameter("appId",app.getId()); q.setMaxResults(1); try { ApplicationArchive o = (ApplicationArchive)q.getSingleResult(); return (ApplicationArchive)o; } catch( NoResultException nre ) { return null; } } @Override public int countDeploymentsByHashAndHashAlg(String hash, String hashAlg) { Query q = entityManager.createQuery("select count(d) " +"from Deployment d " +"left join d.applicationArchive aa " +"where aa.hash=:hash " +"and aa.hashAlgorithm=:hashAlgorithm"); q.setParameter("hash", hash); q.setParameter("hashAlgorithm", hashAlg); try { Number ret = (Number)q.getSingleResult(); return ret.intValue(); } catch( NoResultException nre ) { return 0; } } @Override public int countVersionsByHashAndHashAlg(String hash, String hashAlg) { Query q = entityManager.createQuery("select count(av) " +"from ApplicationVersion av " +"left join av.archive ar " +"where ar.hash=:hash " +"and ar.hashAlgorithm=:hashAlgorithm"); q.setParameter("hash", hash); q.setParameter("hashAlgorithm", hashAlg); try { Number ret = (Number)q.getSingleResult(); return ret.intValue(); } catch( NoResultException nre ) { return 0; } } @Override public int countApplicationArchivesByHashAndHashAlg(String hash, String hashAlg) { Query q = entityManager.createQuery("select count(ar) " +"from ApplicationArchive ar " +"where ar.hash=:hash " +"and ar.hashAlgorithm=:hashAlgorithm "); q.setParameter("hash", hash); q.setParameter("hashAlgorithm", hashAlg); try { Number ret = (Number)q.getSingleResult(); return ret.intValue(); } catch( NoResultException nre ) { return 0; } } @Override public <E extends ModelEntity, T extends ModelEntity> List<T> getOrdered(E entity, String listMethod, Comparator<T> comparator) { EntityManager entityManager = getEntityManager(); entityManager.getTransaction().begin(); entityManager.merge(entity); List<T> ents; try { ents = (List<T>) entity.getClass().getMethod(listMethod).invoke(entity); } catch (Exception e) { throw new PersistenceException(e); } Collections.sort( ents, comparator ); entityManager.getTransaction().commit(); return ents; } // ACCESSORS public void setEntityManager(EntityManager manager) { entityManager = manager; } public EntityManager getEntityManager() { return entityManager; } /** * Amount of time to wait between each refresh retry. */ public void setRefreshRetryInterval(int refreshRetryInterval) { this.refreshRetryInterval = refreshRetryInterval; } /** * Number of times to retry refreshing a model entity before failing the operation. * Using SQLite, sometimes the database file is locked by another process. */ public void setNumberOfRefreshRetries(int numberOfRefreshRetries) { this.numberOfRefreshRetries = numberOfRefreshRetries; } // PRIVATE METHODS private <T extends ModelEntity> void _delete(T entity, List<ProcessingEvent> events) throws PersistenceException { // give the event notifiers an opportunity to act, prior to deletion entity.remove(); entityManager.remove(entity); } private void _refresh(ModelEntity obj2Refresh) { if( !entityManager.contains(obj2Refresh) ) { entityManager.merge(obj2Refresh); } entityManager.refresh(obj2Refresh); } @Override public ModelService begin() { entityManager.getTransaction().begin(); return this; } @Override public ModelService commit() { entityManager.getTransaction().commit(); return this; } @Override public ModelService rollback() { if( entityManager.isOpen() && entityManager.getTransaction().isActive() ) { entityManager.getTransaction().rollback(); } return this; } }