com.google.enterprise.connector.instantiator.ChangeDetectorImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.google.enterprise.connector.instantiator.ChangeDetectorImpl.java

Source

// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.enterprise.connector.instantiator;

import com.google.common.collect.ImmutableMap;
import com.google.enterprise.connector.logging.NDC;
import com.google.enterprise.connector.persist.ConnectorStamps;
import com.google.enterprise.connector.persist.PersistentStore;
import com.google.enterprise.connector.persist.Stamp;
import com.google.enterprise.connector.persist.StoreContext;

import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Checks for changes in a persistent store. Intended to be run both
 * manually to handle local servlet changes, and periodically to check
 * for remote connector manager changes.
 *
 * @see com.google.enterprise.connector.persist.PersistentStore
 * @see ChangeListener
 */
// TODO: Change StoreContext to String and x.getConnectorName() to x.
class ChangeDetectorImpl implements ChangeDetector {
    private final PersistentStore store;
    private final ChangeListener listener;

    /** The stamps from the previous run. */
    private ImmutableMap<StoreContext, ConnectorStamps> inMemoryInventory = ImmutableMap.of();

    /** A sorted set of the keys of {@code inMemoryInventory}. */
    private SortedSet<StoreContext> inMemoryInstances = new TreeSet<StoreContext>();

    /**
     * Constructs the detector.
     *
     * @param store the persistent store to look for changes in
     * @param listener the change listener to notify of changes
     */
    ChangeDetectorImpl(PersistentStore store, ChangeListener listener) {
        this.store = store;
        this.listener = listener;
    }

    @Override
    public synchronized void detect() {
        NDC.push("Change");
        try {
            ImmutableMap<StoreContext, ConnectorStamps> persistentInventory = store.getInventory();
            SortedSet<StoreContext> persistentInstances = new TreeSet<StoreContext>(persistentInventory.keySet());

            // Compare the last known (inMemory) inventory with the new inventory
            // from the persistent store. Notify ChangeListeners of any differences.
            // Save in memory, the new inventory of unchanged items and successfully
            // applied changes.
            inMemoryInventory = compareInventoriesAndNotifyListeners(inMemoryInstances.iterator(),
                    persistentInstances.iterator(), persistentInventory);
            inMemoryInstances = persistentInstances;

        } finally {
            NDC.pop();
        }
    }

    /**
     * Gets the next element of an {@code Iterator} iterator, or
     * {@code null} if there are no more elements.
     *
     * @return the next element or {@code null}
     */
    private StoreContext getNext(Iterator<StoreContext> it) {
        return it.hasNext() ? it.next() : null;
    }

    /**
     * Iterates over the sorted sets of instance names to find additions
     * and deletions. When matching names are found, compare the version
     * stamps for changes in the individual persisted objects.
     *
     * @param mi the sorted keys to the in-memory instances
     * @param pi the sorted keys to the persistent instances
     * @param persistentInventory the persistent object stamps
     * @return a new inventory of stamps, derived from the
     *         persistentInventory, but reflecting instantiation failures.
     */
    private ImmutableMap<StoreContext, ConnectorStamps> compareInventoriesAndNotifyListeners(
            Iterator<StoreContext> mi, Iterator<StoreContext> pi,
            ImmutableMap<StoreContext, ConnectorStamps> persistentInventory) {
        // This map will accumulate items for the new in-memory inventory.
        // Generally, this map will end up being identical to the
        // persistentInventory. However, failed connector instantiations
        // may cause changes to be dropped from this map, so that they may
        // be retried next time around.
        ImmutableMap.Builder<StoreContext, ConnectorStamps> mapBuilder = new ImmutableMap.Builder<StoreContext, ConnectorStamps>();

        StoreContext m = getNext(mi);
        StoreContext p = getNext(pi);
        while (m != null && p != null) {
            // Compare instance names.
            int diff = m.getConnectorName().compareTo(p.getConnectorName());
            NDC.pushAppend((diff < 0 ? m : p).getConnectorName());
            try {
                if (diff == 0) {
                    // Compare the inMemory vs inPStore ConnectorStamps for a
                    // connector instance. Notify ChangeListeners for items whose
                    // Stamps have changed.
                    ConnectorStamps stamps = compareInstancesAndNotifyListeners(m, p, inMemoryInventory.get(m),
                            persistentInventory.get(p));

                    // Remember the new ConnetorStamps for our new inMemory inventory.
                    mapBuilder.put(p, stamps);

                    // Advance to the next connector instance.
                    m = getNext(mi);
                    p = getNext(pi);
                } else if (diff < 0) {
                    listener.connectorRemoved(m.getConnectorName());
                    m = getNext(mi);
                } else { // diff > 0
                    try {
                        listener.connectorAdded(p.getConnectorName(), store.getConnectorConfiguration(p));
                        mapBuilder.put(p, persistentInventory.get(p));
                    } catch (InstantiatorException e) {
                        // Forget about this one and retry on the next time around.
                        pi.remove();
                    }
                    p = getNext(pi);
                }
            } finally {
                NDC.pop();
            }
        }
        while (m != null) {
            NDC.pushAppend(m.getConnectorName());
            try {
                listener.connectorRemoved(m.getConnectorName());
            } finally {
                NDC.pop();
            }
            m = getNext(mi);
        }
        while (p != null) {
            NDC.pushAppend(p.getConnectorName());
            try {
                listener.connectorAdded(p.getConnectorName(), store.getConnectorConfiguration(p));
                mapBuilder.put(p, persistentInventory.get(p));
            } catch (InstantiatorException e) {
                // Forget about this one and retry on the next time around.
                pi.remove();
            } finally {
                NDC.pop();
            }
            p = getNext(pi);
        }
        return mapBuilder.build();
    }

    /**
     * Compares the version stamps for the given instance.  Notify ChangeListeners
     * of any differences.
     *
     * @param m the key for the in-memory instance
     * @param p the key for the persistent instance
     * @param ms the stamps for the in-memory instance
     * @param ps the stamps for the persistent instance
     * @return possibly modified stamps for the persistent instance
     */
    // TODO: When StoreContext becomes String, we only need one key
    // parameter because we will have m.equals(p). NOTE: This may be
    // false now, if the connector type has changed.
    private ConnectorStamps compareInstancesAndNotifyListeners(StoreContext m, StoreContext p, ConnectorStamps ms,
            ConnectorStamps ps) {

        if (compareStamps(ms.getCheckpointStamp(), ps.getCheckpointStamp()) != 0) {
            listener.connectorCheckpointChanged(p.getConnectorName(), store.getConnectorState(p));
        }

        if (compareStamps(ms.getScheduleStamp(), ps.getScheduleStamp()) != 0) {
            listener.connectorScheduleChanged(p.getConnectorName(), store.getConnectorSchedule(p));
        }

        // Save configuration for last, because it may fail.
        if (compareStamps(ms.getConfigurationStamp(), ps.getConfigurationStamp()) != 0) {
            try {
                listener.connectorConfigurationChanged(p.getConnectorName(), store.getConnectorConfiguration(p));
            } catch (InstantiatorException e) {
                // Instantiation of the connector failed. Remember a null configuration
                // stamp so we will try the new configuration again next time through.
                // This is an attempt to handle connectors that fail instantiation
                // due to transient causes (such as a server off-line).
                return new ConnectorStamps(ps.getCheckpointStamp(), null, ps.getScheduleStamp());
            }
        }

        // Return the original stamps.
        return ps;
    }

    /**
     * Compares two version stamps. Stamps may be {@code null}, in which
     * case they are sorted lower than any non-{@code null} object.
     *
     * @param memoryStamp the stamp for the in-memory object
     * @param persistentStamp the stamp for the persistent object
     * @return a negative integer, zero, or a positive integer as the
     * in-memory stamp is less than, equal to, or greater than the
     * persistent stamp
     * @see java.util.Comparator#compare(Object, Object)
     */
    private int compareStamps(Stamp memoryStamp, Stamp persistentStamp) {
        if (memoryStamp == null && persistentStamp == null) {
            return 0;
        } else if (memoryStamp == null) {
            return -1;
        } else if (persistentStamp == null) {
            return +1;
        } else {
            return memoryStamp.compareTo(persistentStamp);
        }
    }
}