org.squashtest.tm.service.internal.advancedsearch.IndexationServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.service.internal.advancedsearch.IndexationServiceImpl.java

Source

/**
 *     This file is part of the Squashtest platform.
 *     Copyright (C) 2010 - 2016 Henix, henix.fr
 *
 *     See the NOTICE file distributed with this work for additional
 *     information regarding copyright ownership.
 *
 *     This 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.
 *
 *     this software 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 this software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.squashtest.tm.service.internal.advancedsearch;

import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.CacheMode;
import org.hibernate.Criteria;
import org.hibernate.FlushMode;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.backend.impl.PostTransactionWorkQueueSynchronization;
import org.hibernate.search.backend.impl.TransactionalWorker;
import org.hibernate.search.backend.impl.WorkQueue;
import org.hibernate.search.batchindexing.MassIndexerProgressMonitor;
import org.hibernate.search.spi.SearchIntegrator;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.squashtest.tm.domain.campaign.IterationTestPlanItem;
import org.squashtest.tm.domain.library.IndexModel;
import org.squashtest.tm.domain.requirement.RequirementVersion;
import org.squashtest.tm.domain.testcase.TestCase;
import org.squashtest.tm.service.advancedsearch.IndexationService;
import org.squashtest.tm.service.campaign.IndexedIterationTestPlanItem;
import org.squashtest.tm.service.configuration.ConfigurationService;
import org.squashtest.tm.service.internal.library.AdvancedSearchIndexingMonitor;

@Service("squashtest.tm.service.IndexationService")
@Transactional
public class IndexationServiceImpl implements IndexationService {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(IndexationServiceImpl.class);

    @PersistenceContext
    private EntityManager em;

    @Inject
    private ConfigurationService configurationService;

    public static final String SQUASH_VERSION_KEY = "squashtest.tm.database.version";

    public static final String REQUIREMENT_INDEXING_DATE_KEY = "lastindexing.requirement.date";
    public static final String TESTCASE_INDEXING_DATE_KEY = "lastindexing.testcase.date";
    public static final String CAMPAIGN_INDEXING_DATE_KEY = "lastindexing.campaign.date";

    public static final String REQUIREMENT_INDEXING_VERSION_KEY = "lastindexing.requirement.version";
    public static final String TESTCASE_INDEXING_VERSION_KEY = "lastindexing.testcase.version";
    public static final String CAMPAIGN_INDEXING_VERSION_KEY = "lastindexing.campaign.version";

    private static final int BATCH_SIZE = 20;

    private static final int MASS_INDEX_BATCH_SIZE = 50;

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");

    @Override
    public IndexModel findIndexModel() {
        IndexModel model = new IndexModel();
        model.setRequirementIndexDate(findIndexDate(REQUIREMENT_INDEXING_DATE_KEY));
        model.setTestCaseIndexDate(findIndexDate(TESTCASE_INDEXING_DATE_KEY));
        model.setCampaignIndexDate(findIndexDate(CAMPAIGN_INDEXING_DATE_KEY));
        model.setRequirementIndexVersion(configurationService.findConfiguration(REQUIREMENT_INDEXING_VERSION_KEY));
        model.setTestcaseIndexVersion(configurationService.findConfiguration(TESTCASE_INDEXING_VERSION_KEY));
        model.setCampaignIndexVersion(configurationService.findConfiguration(CAMPAIGN_INDEXING_VERSION_KEY));
        model.setCurrentSquashVersion(configurationService.findConfiguration(SQUASH_VERSION_KEY));
        return model;
    }

    private Date findIndexDate(String key) {
        String value = configurationService.findConfiguration(key);
        Date date = null;
        if (value != null) {
            try {
                date = dateFormat.parse(value);
            } catch (ParseException e) {

            }
        }
        return date;
    }

    @Override
    public Boolean isIndexedOnPreviousVersion() {
        String currentVersion = configurationService.findConfiguration(SQUASH_VERSION_KEY);
        String testcaseIndexVersion = configurationService.findConfiguration(TESTCASE_INDEXING_VERSION_KEY);
        String requirementIndexVersion = configurationService.findConfiguration(REQUIREMENT_INDEXING_VERSION_KEY);
        String campaignIndexVersion = configurationService.findConfiguration(CAMPAIGN_INDEXING_VERSION_KEY);

        boolean result = currentVersion.equals(requirementIndexVersion)
                && currentVersion.equals(testcaseIndexVersion) && currentVersion.equals(campaignIndexVersion);

        return !result;
    }

    @Override
    public void reindexRequirementVersion(Long requirementVersionId) {
        reindexEntity(RequirementVersion.class, requirementVersionId);
    }

    @Override
    public void reindexRequirementVersions(List<RequirementVersion> requirementVersionList) {
        for (RequirementVersion requirementVersion : requirementVersionList) {
            reindexRequirementVersion(requirementVersion.getId());
        }
    }

    @Override
    public void reindexRequirementVersionsByIds(List<Long> requirementVersionsIds) {
        for (Long id : requirementVersionsIds) {
            reindexRequirementVersion(id);
        }

    }

    @Override
    public void reindexTestCase(Long testCaseId) {
        reindexEntity(TestCase.class, testCaseId);
    }

    @Override
    public void reindexTestCases(List<TestCase> testCaseList) {
        for (TestCase testcase : testCaseList) {
            reindexTestCase(testcase.getId());
        }
    }

    private void reindexEntity(Class<?> T, long id) {
        Session session = getCurrentSession();
        FullTextSession ftSession = Search.getFullTextSession(session);
        Object obj = ftSession.load(T, id);
        ftSession.index(obj);
    }

    // index used in administration
    @Override
    public void indexAll() {
        indexEntities(TestCase.class, RequirementVersion.class, IterationTestPlanItem.class);
    }

    @Override
    public void indexRequirementVersions() {
        indexEntities(RequirementVersion.class);
    }

    @Override
    public void indexTestCases() {
        indexEntities(TestCase.class);
    }

    @Override
    public void indexIterationTestPlanItem() {
        indexEntities(IterationTestPlanItem.class);
    }

    private void indexEntities(Class<?>... T) {
        Session session = getCurrentSession();
        FullTextSession ftSession = Search.getFullTextSession(session);
        MassIndexerProgressMonitor monitor = new AdvancedSearchIndexingMonitor(Arrays.asList(T),
                this.configurationService);
        ftSession.createIndexer(T).purgeAllOnStart(true).threadsToLoadObjects(T.length)
                .typesToIndexInParallel(T.length).batchSizeToLoadObjects(MASS_INDEX_BATCH_SIZE)
                .cacheMode(CacheMode.IGNORE).progressMonitor(monitor).start();

    }

    @Override
    public void batchReindexTc(Collection<Long> tcIdsToIndex) {
        batchReindex(TestCase.class, tcIdsToIndex);
    }

    @Override
    public void batchReindexReqVersion(Collection<Long> reqVersionIdsToIndex) {
        batchReindex(RequirementVersion.class, reqVersionIdsToIndex);

    }

    @Override
    public void batchReindexItpi(Collection<Long> itpisIdsToIndex) {
        batchReindex(IterationTestPlanItem.class, itpisIdsToIndex);
    }

    // Batched versions
    private <T> void batchReindex(Class<T> entity, Collection<Long> ids) {
        if (!ids.isEmpty()) {
            FullTextSession ftSession = getFullTextSession();
            ScrollableResults scroll = getScrollableResults(ftSession, entity, ids);
            doReindex(ftSession, scroll);
        }
    }

    private void doReindex(FullTextSession ftSession, ScrollableResults scroll) {
        // update index going through the search results
        int batch = 0;
        while (scroll.next()) {
            ftSession.index(scroll.get(0)); // indexing of a single entity

            if (++batch % BATCH_SIZE == 0) { // commit batch
                ftSession.flushToIndexes();
                ftSession.clear();
            }
        }
        // commit remaining item
        ftSession.flushToIndexes();
        ftSession.clear();
    }

    private ScrollableResults getScrollableResults(FullTextSession ftSession, Class<?> entity,
            Collection<Long> ids) {
        Criteria query = ftSession.createCriteria(entity);
        query.add(Restrictions.in("id", ids));
        return query.scroll(ScrollMode.FORWARD_ONLY);
    }

    private FullTextSession getFullTextSession() {
        Session session = getCurrentSession();

        // get FullText session
        FullTextSession ftSession = Search.getFullTextSession(session);
        ftSession.setFlushMode(FlushMode.MANUAL);
        ftSession.setCacheMode(CacheMode.IGNORE);

        // Clear the lucene work queue to eliminate lazy init bug for batch processing.
        clearLuceneQueue(ftSession);

        return ftSession;
    }

    // BEWARE dark magic
    private void clearLuceneQueue(FullTextSession ftSession) {
        SearchFactory searchFactory = ftSession.getSearchFactory();
        SearchIntegrator searchIntegrator = searchFactory.unwrap(SearchIntegrator.class);
        TransactionalWorker worker = (TransactionalWorker) searchIntegrator.getWorker();

        try {
            Field synchronizationPerTransactionField = TransactionalWorker.class
                    .getDeclaredField("synchronizationPerTransaction");

            synchronizationPerTransactionField.setAccessible(true);
            @SuppressWarnings("unchecked")
            ConcurrentMap<Object, PostTransactionWorkQueueSynchronization> synchronizationPerTransaction = (ConcurrentMap<Object, PostTransactionWorkQueueSynchronization>) synchronizationPerTransactionField
                    .get(worker);

            Transaction transaction = ftSession.getTransaction();
            PostTransactionWorkQueueSynchronization txSync = synchronizationPerTransaction.get(transaction);

            Field queueField = PostTransactionWorkQueueSynchronization.class.getDeclaredField("queue");
            queueField.setAccessible(true);

            if (txSync != null) {
                WorkQueue queue = (WorkQueue) queueField.get(txSync);
                queue.clear();
            }

        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            LOGGER.debug("Error during indexing", e);
        }

    }

    private Session getCurrentSession() {
        return em.unwrap(Session.class);
    }

}