ome.services.eventlogs.EventLogLoader.java Source code

Java tutorial

Introduction

Here is the source code for ome.services.eventlogs.EventLogLoader.java

Source

/*
 *   $Id$
 *
 *   Copyright 2008 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */

package ome.services.eventlogs;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import ome.api.IQuery;
import ome.model.IObject;
import ome.model.meta.EventLog;
import ome.parameters.Filter;
import ome.parameters.Parameters;
import ome.services.messages.ReindexMessage;
import ome.tools.hibernate.QueryBuilder;
import ome.util.Utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

/**
 * Data access object for the {@link FullTextIndexer} which provides an
 * {@link Iterator} interface for {@link EventLog} instances to be properly
 * indexed. Also supports the concept of batches. After {@link #batchSize}
 * queries,
 * 
 * 
 * @author Josh Moore, josh at glencoesoftware.com
 * @since 3.0-Beta3
 */
public abstract class EventLogLoader implements Iterator<EventLog>, Iterable<EventLog>, ApplicationListener {

    protected final Log log = LogFactory.getLog(getClass());

    /**
     * Currently 100.
     */
    public final static int DEFAULT_BATCH_SIZE = 100;

    protected int batchSize = DEFAULT_BATCH_SIZE;

    /**
     * Set the number of {@link EventLog} instances will be loaded in a single
     * run. If not set, {@link #DEFAULT_BATCH_SIZE} will be used.
     * 
     * @param batchSize
     */
    public void setBatchSize(int batchSize) {
        this.batchSize = batchSize;
    }

    public int getBatchSize() {
        return this.batchSize;
    }

    /**
     * The number of objects which have been returned via {@link #next()}. If
     * {@link #count} is -1, then {@link #hasNext()} will temporarily return
     * null. This signals the end of a batch. A call to {@link #more()} will
     * test whether or not other batches are available.
     */
    private int count = 0;

    /**
     * {@link List} of {@link EventLog} instances which will be consumed before
     * making use of the {@link #query()} method. Used to implement the default
     * {@link #rollback(EventLog)} mechanism.
     */
    private final EventBacklog backlog = new EventBacklog();

    private EventLog eventLog;

    /**
     * Array of class types which will get excluded from indexing.
     */
    protected List<String> excludes = Collections.emptyList();

    /**
     * Spring injector
     */
    public void setExcludes(String[] excludes) {
        this.excludes = Collections.unmodifiableList(Arrays.asList(excludes));
    }

    protected IQuery queryService;

    /**
     * Spring injector
     */
    public void setQueryService(IQuery queryService) {
        this.queryService = queryService;
    }

    /**
     * Tests for available objects. If {@link #count} is -1, then this batch has
     * ended (set in {@link #next()}) and false will be returned,
     * {@link EventBacklog} will be ready to be switched over to an "adding"
     * state if empty, and{@link #count} is also reset so further calls can
     * finish normally; otherwise {@link #query()} is called to load a new
     * {@link #eventLog}. Otherwise, just tests that field for null.
     */
    public boolean hasNext() {

        // If we have an event log, we always return true so that we always
        // have a clean slate. (And it's simply being honest)
        if (eventLog != null) {
            return true;
        }

        // If this is the first call in a batch, give the backlog a chance
        // to make this a backlog (removing-only) batch;
        if (count == 0) {
            backlog.flipState();
        }

        // If we've done this enough, then bail out.
        if (count == batchSize) {
            count = 0;
            return false;
        }
        count++;

        // Do what we can to load an event log
        if (backlog.removingOnly()) {
            eventLog = backlog.remove();
        } else {
            eventLog = query();
        }

        boolean endBatch = eventLog == null;
        if (endBatch) {
            count = 0;
        }
        return !endBatch;
    }

    /**
     * Returns the current {@link #log} instance which may be loaded by a call
     * to {@link #hasNext()} if necessary. If {@link #hasNext()} returns false,
     * a {@link NoSuchElementException} will be thrown.
     */
    public EventLog next() {

        // Consumer should have checked with hasNext
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        // already loaded by call to hasNext() above
        EventLog rv = eventLog;
        eventLog = null;
        return rv;

    }

    public final void remove() {
        throw new UnsupportedOperationException("Cannot remove EventLogs");
    }

    public void rollback(EventLog el) {
        if (excludes.contains(el.getEntityType())) {
            if (log.isDebugEnabled()) {
                log.debug("Skipping rollback of " + el.getEntityType());
            }
        }
        backlog.add(el);
    }

    protected abstract EventLog query();

    public Iterator<EventLog> iterator() {
        return this;
    }

    /**
     * Should return an estimate of how many more {@link EventLog} instances are
     * available for processing. Some implementations may attempt to take extra
     * measures if the number is too large. Use 1 for a constant rather than
     * {@link Long#MAX_VALUE}. Use 0 to stop execution.
     */
    public abstract long more();

    /**
     * Returns the {@link EventLog} with the next id after the given argument or
     * null if none exists. This method will only return "true" {@link EventLog}
     * instances, with a valid id. The {@link #excludes} list is used to filter
     * out unwanted {@link EventLog} isntances.
     */
    public final EventLog nextEventLog(long id) {
        List<String> copy = excludes; // Instead of synchronizing
        QueryBuilder qb = new QueryBuilder();
        qb.select("el");
        qb.from("EventLog", "el");
        qb.where();
        qb.and("el.id > " + id);
        if (copy != null) {
            for (String exclude : copy) {
                qb.and("el.entityType != '" + exclude + "'");
            }
        }
        qb.order("id", true);
        String query = qb.queryString();

        return queryService.findByQuery(query, new Parameters().page(0, 1));
    }

    public final EventLog lastEventLog() {
        return queryService.findByQuery("select el from EventLog el order by id desc", new Parameters().page(0, 1));
    }

    // Re-Indexing
    // =========================================================================

    /**
     * Adds an {@link EventLog} for the given {@link Class} and id to the
     * backlog.
     */
    public boolean addEventLog(Class<? extends IObject> cls, long id) {
        if (excludes.contains(cls.getName())) {
            if (log.isDebugEnabled()) {
                log.debug("Skipping addition of " + cls.getName());
                return false;
            }
        }
        EventLog el = new EventLog();
        el.setEntityId(id);
        el.setEntityType(cls.getName());
        el.setAction("INSERT");
        return backlog.add(el);
    }

    @SuppressWarnings("unchecked")
    public void onApplicationEvent(ApplicationEvent arg0) {
        if (arg0 instanceof ReindexMessage) {
            ReindexMessage<? extends IObject> rm = (ReindexMessage<? extends IObject>) arg0;
            for (IObject obj : rm.objects) {
                Class trueClass = Utils.trueClass(obj.getClass());
                addEventLog(trueClass, obj.getId());
            }
        }
    }
}