/*
* eGov suite of products aim to improve the internal efficiency,transparency,
* accountability and the service delivery of the government organizations.
*
* Copyright (C) <2015> eGovernments Foundation
*
* The updated version of eGov suite of products as by eGovernments Foundation
* is available at http://www.egovernments.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/ or
* http://www.gnu.org/licenses/gpl.html .
*
* In addition to the terms of the GPL license to be adhered to in using this
* program, the following additional terms are to be complied with:
*
* 1) All versions of this program, verbatim or modified must carry this
* Legal Notice.
*
* 2) Any misrepresentation of the origin of the material is prohibited. It
* is required that all modified versions of this material be marked in
* reasonable ways as different from the original version.
*
* 3) This license does not grant any rights to any user of the program
* with regards to rights under trademark law for use of the trade names
* or trademarks of eGovernments Foundation.
*
* In case of any queries, you can reach eGovernments Foundation at contact@egovernments.org.
*/
package org.egov.infstr.services;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.queryparser.classic.QueryParser.Operator;
import org.egov.infra.admin.master.entity.User;
import org.egov.infra.config.core.ApplicationThreadLocals;
import org.egov.infra.exception.ApplicationRuntimeException;
import org.egov.infra.persistence.entity.AbstractAuditable;
import org.egov.infra.validation.exception.ValidationError;
import org.egov.infra.validation.exception.ValidationException;
import org.egov.infstr.models.BaseModel;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.validation.ConstraintViolation;
import javax.validation.Path.Node;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Transactional(readOnly = true)
public class PersistenceService<T, ID extends Serializable> {
private static final Logger LOG = LoggerFactory.getLogger(PersistenceService.class);
private static final String DEFAULT_FIELD = "_hibernate_class";
private Class<T> type;
@Autowired
@Qualifier("entityValidator")
private LocalValidatorFactoryBean entityValidator;
@PersistenceContext
EntityManager entityManager;
public PersistenceService(final Class<T> type) {
this.type = type;
}
public Class<T> getType() {
return this.type;
}
public Session getSession() {
return entityManager.unwrap(Session.class);
}
public void validate(final T model) {
final List<ValidationError> errors = this.validateModel(model);
if (!errors.isEmpty())
throw new ValidationException(errors);
}
public List<ValidationError> validateModel(final T model) {
LOG.debug("Validating Model");
final List<ValidationError> errors = new ArrayList<ValidationError>();
if (model == null) {
errors.add(new ValidationError("", "model.null"));
return errors;
}
final Set<ConstraintViolation<T>> constraintViolations = entityValidator.validate(model);
for (final ConstraintViolation<T> constraintViolation : constraintViolations) {
final Iterator<Node> nodes = constraintViolation.getPropertyPath().iterator();
while (nodes.hasNext())
errors.add(new ValidationError(nodes.next().getName(), constraintViolation.getMessage()));
}
if (model instanceof BaseModel) {
final BaseModel basemodel = (BaseModel) model;
final List<ValidationError> dependentValMessages = basemodel.validate();
if (dependentValMessages != null)
errors.addAll(dependentValMessages);
}
return errors;
}
public T find(final String query, final Object... params) {
final List<T> results = findAllBy(query, params);
return results.isEmpty() ? null : results.get(0);
}
public T find(final String query) {
final Query q = getSession().createQuery(query);
return (T) q.uniqueResult();
}
protected T findById(final ID id) {
return (T) (id == null ? null : getSession().get(this.type, id));
}
public List<T> findAllBy(final String query, final Object... params) {
final Query q = getQueryWithParams(query, params);
return q.list();
}
/**
* @param query
* @param pageNumber
* used to determine the offset from which to return the results
* @param pageSize
* Number of records to be returned in the page. If null then all
* records that match query are returned
* @param params
* @return
*/
public Page findPageBy(final String query, final Integer pageNumber, final Integer pageSize,
final Object... params) {
final Query q = getQueryWithParams(query, params);
return new Page(q, pageNumber, pageSize);
}
private Query getQueryWithParams(final String query, final Object... params) {
final Query q = getSession().createQuery(query);
int index = 0;
for (final Object param : params) {
q.setParameter(index, param);
index++;
}
return q;
}
public List<T> findAllByNamedQuery(final String namedQuery, final Object... params) {
final Query q = getNamedQueryWithParams(namedQuery, params);
return q.list();
}
/**
* @param namedQuery
* @param pageNumber
* used to determine the offset from which to return the results
* @param pageSize
* Number of records to be returned in the page. If null then all
* records that match query are returned
* @param params
* @return Page instance that can be used to implement pagination
*/
public Page findPageByNamedQuery(final String namedQuery, final Integer pageNumber, final Integer pageSize,
final Object... params) {
final Query q = getNamedQueryWithParams(namedQuery, params);
return new Page(q, pageNumber, pageSize);
}
private Query getNamedQueryWithParams(final String namedQuery, final Object... params) {
final Query q = getSession().getNamedQuery(namedQuery);
int index = 0;
for (final Object param : params) {
if (param instanceof Collection)
q.setParameterList(String.valueOf("param_"+index), (Collection)param);
else
q.setParameter(index, param);
index++;
}
return q;
}
public T findByNamedQuery(final String namedQuery, final Object... params) {
final List<T> results = findAllByNamedQuery(namedQuery, params);
return results.isEmpty() ? null : results.get(0);
}
@Transactional
public T persist(final T model) {
validate(model);
getSession().saveOrUpdate(model);
return model;
}
@Transactional
public T merge(final T model) {
validate(model);
return (T) getSession().merge(model);
}
@Transactional
public T create(final T entity) {
validate(entity);
final Long id = (Long) getSession().save(entity);
return (T) getSession().load(this.type, id);
}
public T load(final Serializable id, Class cls) {
return (T) getSession().load(cls, id);
}
@Transactional
public void delete(final T entity) {
getSession().delete(entity);
}
public List<T> findAll() {
return getSession().createCriteria(this.type).list();
}
public List<T> findByExample(final T exampleT) {
final Criteria criteria = getSession().createCriteria(this.type);
return criteria.add(Example.create(exampleT)).list();
}
public T findById(final ID id, final boolean lock) {
return findById(id);
}
public T findByIdWithJoinFetch(final ID id, final String joinFetchPropertyName) {
return (T) getSession().createCriteria(type).setFetchMode(joinFetchPropertyName, FetchMode.JOIN)
.add(Restrictions.idEq(id)).uniqueResult();
}
@Transactional
public T update(final T entity) {
validate(entity);
getSession().update(entity);
return entity;
}
public List<T> findAll(final String... orderByFields) {
final Criteria c = getSession().createCriteria(this.type);
for (final String orderBy : orderByFields)
c.addOrder(Order.asc(orderBy).ignoreCase());
return c.list();
}
public List<T> search(final String queryString, final int pageNumber, final int pageSize) {
return search(queryString, pageNumber, pageSize, PagingStrategy.PAGE);
}
public List<T> search(final String queryString) {
return search(queryString, 0, 0, PagingStrategy.NONE);
}
public String getNamedQuery(final String namedQuery) {
return getSession().getNamedQuery(namedQuery).getQueryString();
}
private List<T> search(final String queryString, final int pageNumber, final int pageSize,
final PagingStrategy paging) {
final QueryParser parser = new QueryParser(DEFAULT_FIELD, new StandardAnalyzer());
parser.setAllowLeadingWildcard(true);
parser.setDefaultOperator(Operator.AND);
final FullTextSession searchSession = getSearchSession();
try {
final FullTextQuery query = searchSession.createFullTextQuery(parser.parse(queryString), this.type);
paging.setup(query, pageNumber, pageSize);
return query.list();
} catch (final ParseException e) {
throw new ApplicationRuntimeException("invalid.search.string", e);
}
}
protected FullTextSession getSearchSession() {
return Search.getFullTextSession(getSession());
}
enum PagingStrategy {
PAGE {
public void setup(final FullTextQuery query, final int pageNumber, final int pageSize) {
query.setFirstResult(pageNumber * pageSize + 1).setMaxResults(pageSize);
}
},
NONE {
};
public void setup(final FullTextQuery query, final int pageNumber, final int pageSize) {
};
}
public void indexEntity() {
final List<T> results = getSession().createCriteria(this.type).list();
final FullTextSession searchSession = getSearchSession();
searchSession.flush();
for (final T entity : results)
searchSession.index(entity);
}
public void addIndexparams(final Map<String, List> indexparams, final String key, final Object... values) {
final List objparams = new ArrayList();
for (final Object value : values)
objparams.add(value);
indexparams.put(key, objparams);
}
public void addFilterCriteriaForObject(final Map<String, List> params, final Criteria c,
final String... orderbyFields) {
for (final Map.Entry<String, List> entry : params.entrySet())
if (entry.getKey().contains("date") || entry.getKey().contains("Date"))
c.add(Restrictions.between(entry.getKey(), entry.getValue().get(0), entry.getValue().get(1)));
else
c.add(Restrictions.eq(entry.getKey(), entry.getValue().get(0)));
for (final String orderBy : orderbyFields)
c.addOrder(Order.asc(orderBy).ignoreCase());
}
/**
* This method is a workaround to apply auditing field values for JPA entity when JPA mixed with hbm based
* entities, this will be removed in future once modules are migrated to JPA annotation.
**/
public void applyAuditing(AbstractAuditable auditable) {
Date currentDate = new Date();
if (auditable.isNew()) {
auditable.setCreatedBy((User)getSession().load(User.class, ApplicationThreadLocals.getUserId()));
auditable.setCreatedDate(currentDate);
}
auditable.setLastModifiedBy((User)getSession().load(User.class, ApplicationThreadLocals.getUserId()));
auditable.setLastModifiedDate(currentDate);
}
public void applyAuditing(BaseModel baseModel) {
Date currentDate = new Date();
if (baseModel.getId() == null) {
baseModel.setCreatedBy((User)getSession().load(User.class, ApplicationThreadLocals.getUserId()));
baseModel.setCreatedDate(currentDate);
}
baseModel.setModifiedBy((User)getSession().load(User.class, ApplicationThreadLocals.getUserId()));
baseModel.setModifiedDate(currentDate);
}
}