org.projectforge.database.DatabaseDao.java Source code

Java tutorial

Introduction

Here is the source code for org.projectforge.database.DatabaseDao.java

Source

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2013 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.database;

import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.apache.commons.lang.ClassUtils;
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.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.SearchFactory;
import org.projectforge.calendar.DayHolder;
import org.projectforge.common.DateHelper;
import org.projectforge.core.AbstractBaseDO;
import org.projectforge.core.ConfigXml;
import org.projectforge.core.ExtendedBaseDO;
import org.projectforge.core.ReindexSettings;
import org.projectforge.timesheet.TimesheetDO;
import org.projectforge.web.calendar.DateTimeFormatter;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import de.micromata.hibernate.history.HistoryEntry;

/**
 * Creates index creation script and re-indexes data-base.
 * @author Kai Reinhard (k.reinhard@micromata.de)
 * 
 */
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class DatabaseDao extends HibernateDaoSupport {
    private static final int MIN_REINDEX_ENTRIES_4_USE_SCROLL_MODE = 2000;

    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(DatabaseDao.class);

    private Date currentReindexRun = null;

    /**
     * Since yesterday and 1,000 newest entries at maximimum.
     * @return
     */
    public static ReindexSettings createReindexSettings(final boolean onlyNewest) {
        if (onlyNewest == true) {
            final DayHolder day = new DayHolder();
            day.add(Calendar.DAY_OF_MONTH, -1); // Since yesterday:
            return new ReindexSettings(day.getDate(), 1000); // Maximum 1,000 newest entries.
        } else {
            return new ReindexSettings();
        }
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
    public String rebuildDatabaseSearchIndices(final Class<?> clazz, final ReindexSettings settings) {
        if (currentReindexRun != null) {
            return "Another re-index job is already running. The job was started at: " + DateTimeFormatter
                    .instance().getFormattedDateTime(currentReindexRun, Locale.ENGLISH, DateHelper.UTC) + " (UTC)";
        }
        final StringBuffer buf = new StringBuffer();
        reindex(clazz, settings, buf);
        return buf.toString();
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
    public void reindex(final Class<?> clazz, final ReindexSettings settings, final StringBuffer buf) {
        if (currentReindexRun != null) {
            buf.append(" (cancelled due to another running index-job)");
            return;
        }
        synchronized (this) {
            try {
                currentReindexRun = new Date();
                buf.append(ClassUtils.getShortClassName(clazz));
                final File file = new File(ConfigXml.getInstance().getApplicationHomeDir() + "/hibernate-search/"
                        + clazz.getName() + "/write.lock");
                if (file.exists() == true) {
                    final Date lastModified = new Date(file.lastModified());
                    final String message;
                    if (System.currentTimeMillis() - file.lastModified() > 60000) { // Last modified date is older than 60 seconds.
                        message = "(*** write.lock with last modification '"
                                + DateTimeFormatter.instance().getFormattedDateTime(lastModified)
                                + "' exists (skip re-index). May-be your admin should delete this file (see log). ***)";
                        log.error(file.getAbsoluteFile() + " " + message);
                    } else {
                        message = "(*** write.lock temporarily exists (skip re-index). ***)";
                        log.info(file.getAbsolutePath() + " " + message);
                    }
                    buf.append(" ").append(message);
                } else {
                    reindex(clazz, settings);
                }
                buf.append(", ");
            } finally {
                currentReindexRun = null;
            }
        }
    }

    /**
     * 
     * @param clazz
     */
    private long reindex(final Class<?> clazz, final ReindexSettings settings) {
        if (settings.getLastNEntries() != null || settings.getFromDate() != null) {
            // OK, only partly re-index required:
            return reindexObjects(clazz, settings);
        }
        // OK, full re-index required:
        if (isIn(clazz, HistoryEntry.class, TimesheetDO.class) == true) {
            // MassIndexer throws LazyInitializationException for some classes, so use it only for the important classes (with most entries):
            return reindexMassIndexer(clazz);
        }
        return reindexObjects(clazz, null);
    }

    private boolean isIn(final Class<?> clazz, final Class<?>... classes) {
        for (final Class<?> cls : classes) {
            if (clazz.equals(cls) == true) {
                return true;
            }
        }
        return false;
    }

    private long reindexObjects(final Class<?> clazz, final ReindexSettings settings) {
        final Session session = getSession();
        Criteria criteria = createCriteria(session, clazz, settings, true);
        final Long number = (Long) criteria.uniqueResult(); // Get number of objects to re-index (select count(*) from).
        final boolean scrollMode = number > MIN_REINDEX_ENTRIES_4_USE_SCROLL_MODE ? true : false;
        log.info("Starting re-indexing of " + number + " entries (total number) of type " + clazz.getName()
                + " with scrollMode=" + scrollMode + "...");
        final int batchSize = 1000;// NumberUtils.createInteger(System.getProperty("hibernate.search.worker.batch_size")
        final FullTextSession fullTextSession = Search.getFullTextSession(session);
        fullTextSession.setFlushMode(FlushMode.MANUAL);
        fullTextSession.setCacheMode(CacheMode.IGNORE);
        long index = 0;
        if (scrollMode == true) {
            // Scroll-able results will avoid loading too many objects in memory
            criteria = createCriteria(fullTextSession, clazz, settings, false);
            final ScrollableResults results = criteria.scroll(ScrollMode.FORWARD_ONLY);
            while (results.next() == true) {
                final Object obj = results.get(0);
                if (obj instanceof ExtendedBaseDO<?>) {
                    ((ExtendedBaseDO<?>) obj).recalculate();
                }
                fullTextSession.index(obj); // index each element
                if (index++ % batchSize == 0)
                    session.flush(); // clear every batchSize since the queue is processed
            }
        } else {
            criteria = createCriteria(session, clazz, settings, false);
            final List<?> list = criteria.list();
            for (final Object obj : list) {
                if (obj instanceof ExtendedBaseDO<?>) {
                    ((ExtendedBaseDO<?>) obj).recalculate();
                }
                fullTextSession.index(obj);
                if (index++ % batchSize == 0)
                    session.flush(); // clear every batchSize since the queue is processed
            }
        }
        final SearchFactory searchFactory = fullTextSession.getSearchFactory();
        searchFactory.optimize(clazz);
        log.info("Re-indexing of " + index + " objects of type " + clazz.getName() + " done.");
        return index;
    }

    /**
     * 
     * @param clazz
     */
    private long reindexMassIndexer(final Class<?> clazz) {
        final Session session = getSession();
        final Criteria criteria = createCriteria(session, clazz, null, true);
        final Long number = (Long) criteria.uniqueResult(); // Get number of objects to re-index (select count(*) from).
        log.info("Starting (mass) re-indexing of " + number + " entries of type " + clazz.getName() + "...");
        final FullTextSession fullTextSession = Search.getFullTextSession(session);
        try {
            fullTextSession.createIndexer(clazz)//
                    .batchSizeToLoadObjects(25) //
                    //.cacheMode(CacheMode.NORMAL) //
                    .threadsToLoadObjects(5) //
                    //.threadsForIndexWriter(1) //
                    .threadsForSubsequentFetching(20) //
                    .startAndWait();
        } catch (final InterruptedException ex) {
            log.error("Exception encountered while reindexing: " + ex.getMessage(), ex);
        }
        final SearchFactory searchFactory = fullTextSession.getSearchFactory();
        searchFactory.optimize(clazz);
        log.info("Re-indexing of " + number + " objects of type " + clazz.getName() + " done.");
        return number;
    }

    private Criteria createCriteria(final Session session, final Class<?> clazz, final ReindexSettings settings,
            final boolean rowCount) {
        final Criteria criteria = session.createCriteria(clazz);
        if (rowCount == true) {
            criteria.setProjection(Projections.rowCount());
        } else {
            if (settings != null) {
                if (settings.getLastNEntries() != null) {
                    criteria.addOrder(Order.desc("id")).setMaxResults(settings.getLastNEntries());
                }
                String lastUpdateProperty = null;
                if (AbstractBaseDO.class.isAssignableFrom(clazz) == true) {
                    lastUpdateProperty = "lastUpdate";
                } else if (HistoryEntry.class.isAssignableFrom(clazz) == true) {
                    lastUpdateProperty = "timestamp";
                }
                if (lastUpdateProperty != null && settings.getFromDate() != null) {
                    criteria.add(Restrictions.ge(lastUpdateProperty, settings.getFromDate()));
                }
            }
        }
        return criteria;
    }
}