/*
###############################################################################
# #
# 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;
}
}