All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.lucene.LIndexIo Maven / Gradle / Ivy

There is a newer version: 2.8.1
Show newest version
/**
 * Copyright (C) 2009 Authors
 * 
 * This file is part of Ebean.
 * 
 * Ebean 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 2.1 of the License, or
 * (at your option) any later version.
 *  
 * Ebean 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 Ebean; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA  
 */
package com.avaje.ebeaninternal.server.lucene;

import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.PersistenceException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import com.avaje.ebean.Junction;
import com.avaje.ebean.QueryListener;
import com.avaje.ebean.Query.UseIndex;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.lucene.LIndexWork.WorkType;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.transaction.IndexEvent;

public class LIndexIo {

    private static final Logger logger = Logger.getLogger(LIndexIo.class.getName());
    
    private final LuceneIndexManager manager;
    
    private final String indexDir;
    
    private final LIndex index;
    
    private final Analyzer analyzer;
    
    private final MaxFieldLength maxFieldLength;
    
    private final Class beanType;
    
    private final OrmQueryDetail ormQueryDetail;
    
    private final Directory directory;
    
    private final BeanDescriptor beanDescriptor;
    
    private final IndexWriter indexWriter;
    
    private final LIndexIoSearcher ioSearcher;
    
    private final HoldAwareIndexDeletionPolicy commitDeletionPolicy;
    
    private final String[] updateProps;
    
    private final Object writeMonitor = new Object();
    
    private final Object workQueueMonitor = new Object();
    
    private final ArrayList workQueue = new ArrayList();
    
    private final ArrayList notifyCommitRunnables = new ArrayList();
    
    private long lastUpdateTime;

    private long queueCommitStart;
    
    private int queueCommitCount;
    
    private int totalCommitCount;
    
    private long totalCommitNanos;
    
    private long totalPostCommitNanos;

    public LIndexIo(LuceneIndexManager manager, String indexDir, LIndex index, String[] updateProps) throws IOException {
        this.manager = manager;
        this.indexDir = indexDir;
        this.index = index;
        this.updateProps = updateProps;
        this.analyzer = index.getAnalyzer();
        this.maxFieldLength = index.getMaxFieldLength();
        this.beanType = index.getBeanType();
        this.ormQueryDetail = index.getOrmQueryDetail();
        this.directory = createDirectory();
        this.beanDescriptor = index.getBeanDescriptor();
        
        this.commitDeletionPolicy = new HoldAwareIndexDeletionPolicy(indexDir);
        this.indexWriter = createIndexWriter();
        
        this.ioSearcher = createIoSearcher();
    }
    
    public LIndexVersion getLastestVersion(){
        return ioSearcher.getLastestVersion();
    }

    public long getLastVersion() {
        return commitDeletionPolicy.getLastVersion();
    }
    
    public LIndexCommitInfo obtainLastIndexCommitIfNewer(long remoteIndexVersion) {
        return commitDeletionPolicy.obtainLastIndexCommitIfNewer(remoteIndexVersion);
    }
    
    public File getIndexDir() {
        return new File(indexDir);
    }
    
    public LIndexFileInfo getLocalFile(String fileName) {

        File f = new File(indexDir, fileName);
        return new LIndexFileInfo(f);
    }
    
    public void refresh(boolean nearRealTime) {
        ioSearcher.refresh(nearRealTime);
    }
    
    
    public LIndexFileInfo getFile(long remoteIndexVersion, String fileName) {
        
        commitDeletionPolicy.touch(remoteIndexVersion);
        
        File f = new File(indexDir, fileName);
        return new LIndexFileInfo(f);
    }
    
    public void releaseIndexCommit(long remoteIndexVersion) {
        commitDeletionPolicy.releaseIndexCommit(remoteIndexVersion);
    }
    
    public void shutdown() {
        synchronized (writeMonitor) { 
            try {
                if (queueCommitStart > 0){
                    indexWriter.commit();
                }
            } catch (Exception e) {
                String msg = "Error committing queued changes for IndexWriter for "+indexDir;
                logger.log(Level.SEVERE, msg, e);
                // Also send to syserr during shutdown
                e.printStackTrace();
            } finally {
                try {
                    indexWriter.close();                
                } catch (Exception e) {
                    String msg = "Error closing IndexWriter for "+indexDir;
                    logger.log(Level.SEVERE, msg, e);
                    // Also send to syserr during shutdown
                    e.printStackTrace();
                }
            }
        }
    }
    
    protected void manage(LuceneIndexManager indexManager) {
        processWorkQueue();
        commit(false);
    }
    
    protected void addWorkToQueue(LIndexWork work){
        synchronized (workQueueMonitor) {
            workQueue.add(work);
        }
    }
    
    @SuppressWarnings("unchecked")
    protected void processWorkQueue() {
        synchronized (workQueueMonitor) {
            if (!workQueue.isEmpty()){
                
                WorkType maxWorkType = null;
                
                for (int i = 0; i < workQueue.size(); i++) {
                    LIndexWork work = workQueue.get(i);
                    if (maxWorkType == null || maxWorkType.ordinal() < work.getWorkType().ordinal()){
                        maxWorkType = work.getWorkType();
                    }
                }
                List workQueueClone = (List)workQueue.clone();
                workQueue.clear();
                
                Callable workCallable = getWorkCallable(maxWorkType, workQueueClone);
                FutureTask ft = new FutureTask(workCallable);
                
                for (int i = 0; i < workQueueClone.size(); i++) {
                    workQueueClone.get(i).getFuture().setTask(ft);
                }
                
                manager.getServer().getBackgroundExecutor().execute(ft);
                
            }
        }
    }
    
    private Callable getWorkCallable(WorkType maxWorkType, List workQueueClone) {
        switch (maxWorkType) {
        case REBUILD:
            return newRebuildCallable(workQueueClone);
        case QUERY_UPDATE:
            return newQueryUpdateCallable(workQueueClone);
        case TXN_UPDATE:
            return newTxnUpdateCallable(workQueueClone);

        default:
            throw new IllegalStateException("Unknown workType "+maxWorkType);
        }
    }
    
    private Callable newTxnUpdateCallable(List workQueueClone) {
        
        final List updates = workQueueClone;
        
        return new Callable() {
            public String toString() {
                return "TxnUpdate";
            }
            
            public Integer call() throws IOException {
                
                int totalDocs = 0;
                
                for (int i = 0; i < updates.size(); i++) {
                    LIndexWork lIndexWork = updates.get(i);
                    IndexUpdates indexUpdates = lIndexWork.getIndexUpdates();
                    LIndexDeltaHandler h = createDeltaHandler(indexUpdates);
                    totalDocs += h.process();
                }
                
                queueCommit(updates);
                
                return totalDocs;
            }
        };
    }
    
    private Callable newRebuildCallable(List workQueueClone) {
        return new QueryUpdater(true, workQueueClone);
    }
    
    private Callable newQueryUpdateCallable(List workQueueClone) {
        return new QueryUpdater(false, workQueueClone);
    }
    
    class QueryUpdater implements Callable {
        
        private final boolean rebuild;
        private final List workQueueClone;
        
        private QueryUpdater(boolean rebuild, List workQueueClone){
            this.rebuild = rebuild;
            this.workQueueClone = workQueueClone;
        }
        
        public String toString() {
            return rebuild ? "Rebuild" : "QueryUpdate";
        }
        
        public Integer call() throws Exception {
            if (rebuild){
                return rebuildIndex(workQueueClone);
            } else {
                return updateIndex(workQueueClone);
            }
        }
    }

    private LIndexDeltaHandler createDeltaHandler(IndexUpdates indexUpdates) {
        
        LIndexSearch search = getIndexSearch();
        IndexWriter indexWriter = this.indexWriter;
        DocFieldWriter docFieldWriter = index.createDocFieldWriter();
        return new LIndexDeltaHandler(index, search, indexWriter, analyzer, beanDescriptor, docFieldWriter, indexUpdates);
    }
    
    public LIndexSearch getIndexSearch() {
        return ioSearcher.getIndexSearch();
    }
    
//    public void commitQueuedChanges(long freqMillis) {
//        synchronized (writeMonitor) {
//            if (queueCommitStart > 0){
//                if (freqMillis == 0 || (System.currentTimeMillis() - freqMillis) > queueCommitStart ){
//                    commit(false);
//                }
//            }
//        }
//    }

    /**
     * Queue a commit for execution later via the Lucene Manager thread.
     */
    private void queueCommit(List workQueueClone) {
        synchronized (workQueueMonitor) {
            if (queueCommitStart == 0){
                queueCommitStart = System.currentTimeMillis();
            }
            queueCommitCount++;
            
            for (LIndexWork w : workQueueClone) {
                // register so that on the next commit these are run
                notifyCommitRunnables.add(w.getFuture().getCommitRunnable());
            }
        }
    }
    
    protected void addNotifyCommitRunnable(Runnable r) {
        synchronized (workQueueMonitor) {   
            notifyCommitRunnables.add(r);
        }
    }
    
    protected long getQueueCommitStart(boolean reset) {
        synchronized (workQueueMonitor) {   
            long start = this.queueCommitStart;
            if (reset){
                this.queueCommitStart = 0;
                this.queueCommitCount = 0;
            }
            return start;
        }
    }
    
    /**
     * Invoke a commit if there are uncommitted changes. Return true if commit
     * occurred or false if no commit was required.
     */
    private boolean commit(boolean force) {
        
        synchronized (writeMonitor) { 
            
            long start = 0;
            long count = 0;
            ArrayList notifyRunnables = new ArrayList();
            
            synchronized (workQueueMonitor) {
                start = queueCommitStart;
                count = queueCommitCount;
                queueCommitStart = 0;
                queueCommitCount = 0;
                notifyRunnables.addAll(notifyCommitRunnables);
                notifyCommitRunnables.clear();
            }
            
            try {
                if (!force && start == 0){
                    // no pending uncommitted changes
                    // so just return false
                    
                    if (!notifyRunnables.isEmpty()){
                        // Add notifyRunnables back onto the notify queue
                        for (int i = 0; i < notifyRunnables.size(); i++) {
                            addNotifyCommitRunnable(notifyRunnables.get(i));
                        }                        
                    }
                    return false;
                }
                if (logger.isLoggable(Level.INFO)){
                    String delayMsg;
                    if (queueCommitStart > 0){
                        long delay = System.currentTimeMillis()-start;
                        delayMsg = " queueDelayMillis:"+delay+" queueCount:"+count;
                    } else {
                        delayMsg = "";
                    }
                    String m = "Lucene commit "+indexDir+delayMsg;
                    logger.info(m);
                }
                long nanoStart = System.nanoTime();
                
                // do the actual commit
                indexWriter.commit();


                long nanoCommit = System.nanoTime();
                long nanoCommitExe = nanoCommit - nanoStart;

                // notify the searcher
                ioSearcher.postCommit();
                
                for (int i = 0; i < notifyRunnables.size(); i++) {
                    notifyRunnables.get(i).run();
                }
                
                long nanoPostCommitExe = System.nanoTime() - nanoCommitExe;
                
                totalCommitCount++;
                totalCommitNanos += nanoCommitExe;
                totalPostCommitNanos += nanoPostCommitExe;
                
                IndexEvent indexEvent = new IndexEvent(IndexEvent.COMMIT_EVENT, index.getName());
                manager.notifyCluster(indexEvent);
                
                return true;
                
            } catch (IOException e) {
                String msg = "Error committing changes on index "+indexDir;
                throw new PersistenceLuceneException(msg, e);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private int rebuildIndex(List workQueueClone) throws IOException {
        synchronized (writeMonitor) { 
            
            logger.info("Lucene rebuild "+indexDir);
            
            //TODO: Parallel index rebuild
            try {
                indexWriter.deleteAll();
                lastUpdateTime = System.currentTimeMillis();
                SpiQuery query = createQuery();
                
                WriteListener writeListener = new WriteListener(index, indexWriter, false);
                query.setListener(writeListener);
                
                manager.getServer().findList(query, null);
                 
                return writeListener.getCount();
                
            } finally {
                queueCommit(workQueueClone);
                commit(false);
            }
        }
    }
        
    @SuppressWarnings("unchecked")
    private int updateIndex(List workQueueClone) throws IOException {
        synchronized (writeMonitor) { 
            
            logger.info("Lucene update "+indexDir);
            
            try {
                long updateTime = System.currentTimeMillis();
                SpiQuery query = createUpdateQuery();
                lastUpdateTime = updateTime;
                
                WriteListener writeListener = new WriteListener(index, indexWriter, true);
                query.setListener(writeListener);
                
                manager.getServer().findList(query, null);
                 
                return writeListener.getCount();
                
            } finally {
                queueCommit(workQueueClone);
            }
        }
    }
    
    private SpiQuery createUpdateQuery() {
        
        SpiQuery q = createQuery();
        Junction disjunction = q.where().disjunction();
        
        Timestamp lastUpdate = new Timestamp(lastUpdateTime);
        
        for (int i = 0; i < updateProps.length; i++) {
            disjunction.ge(updateProps[i], lastUpdate);
        }
        
        return q;
    }
    
    protected SpiQuery createQuery() {
        
        SpiEbeanServer server = manager.getServer();
        SpiQuery query = (SpiQuery)server.createQuery(beanType);
        query.setUseIndex(UseIndex.NO);
        query.getDetail().tuneFetchProperties(ormQueryDetail);
        
        return query;
    }
    
    private Directory createDirectory() throws IOException {
        File dir = new File(indexDir);
        return FSDirectory.open(dir); 
    }
    
    private IndexWriter createIndexWriter() {
        try {
            boolean create = true;
            return new IndexWriter(directory, analyzer, create, commitDeletionPolicy, maxFieldLength);
        } catch (IOException e) {
            String msg = "Error getting Lucene IndexWriter for " + indexDir;
            throw new PersistenceLuceneException(msg, e);
        }
    }
    
    private LIndexIoSearcher createIoSearcher() {
        
        return new LIndexIoSearcherDefault(indexWriter, index.getName());
    }
  
    @SuppressWarnings("rawtypes")
    private static class WriteListener implements QueryListener {

        private final boolean updateMode;
        private final LIndex index;
        private final BeanDescriptor beanDescriptor;
        private final IndexWriter indexWriter;
        private final DocFieldWriter docFieldWriter;
        private final Document document = new Document();
        private int count;
        
        private WriteListener(LIndex index,IndexWriter indexWriter, boolean updateMode) {
            this.updateMode = updateMode;
            this.index = index;
            this.beanDescriptor = index.getBeanDescriptor();
            this.indexWriter = indexWriter;
            this.docFieldWriter = index.createDocFieldWriter();
        }
        
        public void process(Object bean) {
            try {
                if (updateMode) {
                    Object id = beanDescriptor.getId(bean);
                    Term term = index.createIdTerm(id);
                    indexWriter.deleteDocuments(term);
                }
                docFieldWriter.writeValue(bean, document);
                indexWriter.addDocument(document);
                count++;
            } catch (Exception e) {
                throw new PersistenceException(e);
            }
        }

        public int getCount() {
            return count;
        }
        
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy