/*
* C O P Y R I G H T N O T I C E
* -----------------------------------------------------------------------
* Copyright (c) 2011-2012 InfoClinika, Inc. 5901 152nd Ave SE, Bellevue, WA 98006,
* United States of America. (425) 442-8058. http://www.infoclinika.com.
* All Rights Reserved. Reproduction, adaptation, or translation without prior written permission of InfoClinika, Inc. is prohibited.
* Unpublished--rights reserved under the copyright laws of the United States. RESTRICTED RIGHTS LEGEND Use, duplication or disclosure by the
*/
package com.infoclinika.mssharing.search;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.infoclinika.mssharing.model.PaginationItems;
import com.infoclinika.mssharing.model.Searcher;
import com.infoclinika.mssharing.model.internal.CriteriaRules;
import com.infoclinika.mssharing.model.internal.entity.*;
import com.infoclinika.mssharing.model.internal.read.Transformers;
import com.infoclinika.mssharing.model.internal.repository.UserRepository;
import com.infoclinika.mssharing.model.read.DashboardReader;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.hibernate.CacheMode;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.query.dsl.TermMatchingContext;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.concurrent.Future;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.FluentIterable.from;
import static com.infoclinika.mssharing.model.internal.read.Transformers.PagedItemsTransformer.resolve;
import static com.infoclinika.mssharing.model.read.DashboardReader.InstrumentItem;
/**
* @author Stanislav Kurilin
*/
public class SearcherImpl implements Searcher {
private final Transformers transformers;
private final UserRepository userRepository;
private EntityManager entityManager;
private CriteriaRules criteriaRules;
private Future<?> start;
private boolean useHibernateSearchPaging = false;
@Inject
public SearcherImpl(Transformers transformers, UserRepository userRepository, CriteriaRules criteriaRules) {
this.transformers = transformers;
this.userRepository = userRepository;
this.criteriaRules = criteriaRules;
}
@PostConstruct
public void initSearcher() {
FullTextEntityManager fullTextSession = org.hibernate.search.jpa.Search.getFullTextEntityManager(entityManager);
start = fullTextSession.createIndexer()
.purgeAllOnStart(true)
.cacheMode(CacheMode.IGNORE)
.optimizeAfterPurge(true)
.optimizeOnFinish(true)
.batchSizeToLoadObjects(25)
.threadsToLoadObjects(5)
.threadsForSubsequentFetching(20).start();
}
@Override
@Transactional
public ImmutableList<DashboardReader.ProjectLine> projects(long actor, String query) {
final Criteria criteria = getProjectsCriteria(actor);
Iterable<Project> projects = search(criteria, Project.class, query, ImmutableSet.of("areaOfResearch", "description"));
return from(projects).transform(transformers.projectTransformer).toImmutableList();
}
private Criteria getCriteria(Class<?> persistentClass, String alias) {
final Session session = getSession();
return session.createCriteria(persistentClass, alias);
}
@Override
@Transactional
public ImmutableList<DashboardReader.ExperimentLine> experiments(long actor, String query) {
final Criteria criteria = getExperimentsCriteria(actor);
Iterable<Experiment> experiments = search(criteria, Experiment.class, query, ImmutableSet.of("experimentData_description"));
return from(experiments).transform(transformers.experimentOwnerTransformer.apply(actor)).toImmutableList();
}
@Override
@Transactional
public ImmutableList<DashboardReader.FileLine> files(long actor, String query) {
final Criteria criteria = getFilesCriteria(actor);
Iterable<FileMetaData> files = search(criteria, FileMetaData.class, query, ImmutableSet.of("labels"));
return transformers.transformFiles(from(files)).toImmutableList();
}
@Override
@Transactional
public ImmutableList<InstrumentItem> instruments(long actor, String query) {
final Criteria criteria = getInstrumentsCriteria(actor);
Iterable<Instrument> instruments = search(criteria, Instrument.class, query, ImmutableSet.of("hplc", "peripherals", "serialNumber"));
final User user = checkNotNull(userRepository.findOne(actor));
return from(instruments).transform(transformers.instrumentTransformer(user)).toImmutableList();
}
@Override
@Transactional
public PaginationItems.PagedItem<DashboardReader.ProjectLine> pagedProjects(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria criteria = getProjectsCriteria(actor);
final Iterable<Project> items = pagedSearch(criteria, Project.class, ImmutableSet.of("areaOfResearch", "description"), pagedItemInfo);
int count = countProjects(actor, pagedItemInfo);
return new PaginationItems.PagedItem<DashboardReader.ProjectLine>(
count/pagedItemInfo.items,
count,
from(items).transform(transformers.projectTransformer).toImmutableSet()
);
}
private Session getSession() {
return entityManager.unwrap(Session.class);
}
@Override
@Transactional
public PaginationItems.PagedItem<DashboardReader.ExperimentLine> pagedExperiments(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria criteria = getExperimentsCriteria(actor);
final Iterable<Experiment> items = pagedSearch(criteria, Experiment.class, ImmutableSet.of("experimentData_description"), pagedItemInfo);
int count = countExperiments(actor, pagedItemInfo);
return new PaginationItems.PagedItem<DashboardReader.ExperimentLine>(
count/pagedItemInfo.items,
count,
from(items).transform(transformers.experimentOwnerTransformer.apply(actor)).toImmutableSet()
);
}
@Override
@Transactional
public PaginationItems.PagedItem<DashboardReader.FileLine> pagedFiles(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria criteria = getFilesCriteria(actor);
int count = countFiles(actor, pagedItemInfo);
final Iterable<FileMetaData> items = pagedSearch(criteria, FileMetaData.class, ImmutableSet.of("labels"), pagedItemInfo);
return new PaginationItems.PagedItem<DashboardReader.FileLine>(
count/pagedItemInfo.items,
count,
transformers.transformFiles(from(items)).toImmutableSet()
);
}
@Override
@Transactional
public PaginationItems.PagedItem<InstrumentItem> pagedInstruments(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria criteria = getInstrumentsCriteria(actor);
Iterable<Instrument> instruments = pagedSearch(criteria, Instrument.class, ImmutableSet.of("hplc", "peripherals", "serialNumber"), pagedItemInfo);
final User user = checkNotNull(userRepository.findOne(actor));
int count = countInstruments(actor, pagedItemInfo);
return new PaginationItems.PagedItem<InstrumentItem>(
count/pagedItemInfo.items,
count,
from(instruments).transform(transformers.instrumentTransformer(user)).toImmutableSet()
);
}
@Override
@Transactional
public Count getItemsCount(PaginationItems.PagedItemInfo pagedItemInfo, long actor) {
final int instruments = countInstruments(actor, pagedItemInfo);
final int experiments = countExperiments(actor, pagedItemInfo);
final int files = countFiles(actor, pagedItemInfo);
final int projects = countProjects(actor, pagedItemInfo);
return new Count(instruments, projects, files, experiments);
}
private <T> Iterable<T> search(final Criteria query, final Class<T> clazz, String keyword, Iterable<String> additionalFields) {
final FullTextQuery fullTextQuery = getFullTextQuery(query, clazz, keyword, additionalFields);
//noinspection unchecked
return Iterables.transform(fullTextQuery.getResultList(), new Function() {
@Override
public Object apply(Object input) {
if (input.getClass().isAssignableFrom(clazz)) return input;
throw new AssertionError(String.format("Expected: %s. Actual: %s", clazz, input.getClass()));
}
});
}
private <T> Iterable<T> pagedSearch(final Criteria query, final Class<T> clazz, Iterable<String> additionalFields, final PaginationItems.PagedItemInfo info) {
final FullTextQuery fullTextQuery = getFullTextQuery(query, clazz, info.filterQuery, additionalFields);
fullTextQuery.setSort(new Sort(new SortField(resolve(clazz, info.sortingField) + ".sort", SortField.STRING, !info.isSortingAsk)));
if(useHibernateSearchPaging) {
return getHibernatePagingResult(clazz, info, fullTextQuery);
}
return getNonHibernatePagingResult(clazz, info, fullTextQuery);
}
private <T> Iterable<T> getNonHibernatePagingResult(final Class<T> clazz, PaginationItems.PagedItemInfo info, FullTextQuery fullTextQuery) {
final List list = fullTextQuery.getResultList();
//noinspection unchecked
return from(
Iterables.transform(list, new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input.getClass().isAssignableFrom(clazz)) return input;
throw new AssertionError(String.format("Expected: %s. Actual: %s", clazz, input.getClass()));
}
})
).skip(info.items * info.page).limit(info.items);
}
/**
* Currently return incorrect result
*
* @see org.hibernate.search.query.engine.impl.HSQueryImpl#queryEntityInfos
* @see org.hibernate.search.query.hibernate.impl.QueryLoader#executeLoad
* @see org.hibernate.search.query.hibernate.impl.CriteriaObjectsInitializer#initializeObjects - pay attention here
*
*/
private <T> Iterable<T> getHibernatePagingResult(final Class<T> clazz, PaginationItems.PagedItemInfo info, FullTextQuery fullTextQuery) {
fullTextQuery.setFirstResult(info.items * info.page).setMaxResults(info.items);
final List list = fullTextQuery.getResultList();
//noinspection unchecked
return from(Iterables.transform(list, new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input.getClass().isAssignableFrom(clazz)) return input;
throw new AssertionError(String.format("Expected: %s. Actual: %s", clazz, input.getClass()));
}
}));
}
private <T> int countSearch(Criteria countQuery, final Class<T> clazz, Iterable<String> additionalFields, final PaginationItems.PagedItemInfo info) {
final FullTextQuery count = getFullTextQuery(countQuery, clazz, info.filterQuery, additionalFields);
//count.setProjection("id");
return count.getResultList().size();
}
private <T> FullTextQuery getFullTextQuery(Criteria query, Class<T> clazz, String keyword, Iterable<String> additionalFields) {
final FullTextEntityManager session = Search.getFullTextEntityManager(entityManager);
QueryBuilder qb = session.getSearchFactory()
.buildQueryBuilder().forEntity(clazz).get();
String[] searchTerms = keyword.split(" ");
final TermMatchingContext context = qb.keyword().wildcard().onField("name");
for (String field : additionalFields) {
context.andField(field);
}
BooleanJunction<BooleanJunction> bool = qb.bool();
for (String searchTerm : searchTerms) {
bool.must(context.matching(String.format("*%s*", searchTerm.toLowerCase())).createQuery());
}
final FullTextQuery fullTextQuery = session.createFullTextQuery(bool.createQuery(), clazz);
fullTextQuery.setCriteriaQuery(query);
return fullTextQuery;
}
@Override
public boolean isSearchEnabled() {
return start.isDone();
}
@Override
public void setHibernateSearchPaging(boolean useHibernateSearchPaging) {
this.useHibernateSearchPaging = useHibernateSearchPaging;
}
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
private Criteria getFilesCriteria(long actor) {
return getCriteria(FileMetaData.class, "f")
.createAlias("f.instrument", "instrument")
.add(
Restrictions.or(
Restrictions.eq("owner.id", actor),
criteriaRules.isUserOperatorOfInstrumentForFile(actor, "instrument.id"),
criteriaRules.isFileAvailableFromExperiment(actor, "f.id")
)
);
}
private Criteria getInstrumentsCriteria(long actor) {
final Criteria criteria = getCriteria(Instrument.class, "i");
criteria.createAlias("i.lab", "lab")
.createAlias("lab.labMemberships", "lm")
.add(Restrictions.in("lm.user.id", ImmutableSet.of(actor)));
return criteria;
}
private Criteria getExperimentsCriteria(long actor) {
final Criteria criteria = getCriteria(Experiment.class, "e");
criteria.createAlias("e.project", "p").createAlias("e.creator", "creator");
criteriaRules.joinCollaborators(criteria, "p").add(Restrictions.disjunction()
.add(Restrictions.eq("p.creator.id", actor))
.add(criteriaRules.isNotOwnedProjectAvailable(actor)));
return criteria;
}
private Criteria getProjectsCriteria(long actor) {
final Criteria criteria = getCriteria(Project.class, "p" )
.createAlias("p.creator", "creator");
criteriaRules.joinCollaborators(criteria, "p")
.add(
Restrictions.or(
Restrictions.eq("p.creator.id", actor),
criteriaRules.isNotOwnedProjectAvailable(actor)
)
);
return criteria;
}
private int countExperiments(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria countQuery = getExperimentsCriteria(actor)/*.setProjection(Projections.rowCount())*/;
return countSearch(countQuery, Experiment.class, ImmutableSet.of("experimentData_description"), pagedItemInfo);
}
private int countProjects(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria countQuery = getProjectsCriteria(actor)/*.setProjection(Projections.rowCount())*/;
return countSearch(countQuery, Project.class, ImmutableSet.of("areaOfResearch", "description"), pagedItemInfo);
}
private int countFiles(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria countQuery = getFilesCriteria(actor)/*.setProjection(Projections.rowCount())*/;
return countSearch(countQuery, FileMetaData.class, ImmutableSet.of("labels"), pagedItemInfo);
}
private int countInstruments(long actor, PaginationItems.PagedItemInfo pagedItemInfo) {
final Criteria countQuery = getInstrumentsCriteria(actor)/*.setProjection(Projections.rowCount())*/;
return countSearch(countQuery, Instrument.class, ImmutableSet.of("hplc", "peripherals", "serialNumber"), pagedItemInfo);
}
}