AutoSaveListener.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.commons.configuration2.builder;

import org.apache.commons.configuration2.event.ConfigurationEvent;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileHandler;
import org.apache.commons.configuration2.io.FileHandlerListenerAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * A listener class implementing an auto save mechanism for file-based configurations.
 * </p>
 * <p>
 * Instances of this class are used by {@link FileBasedConfigurationBuilder} to save their managed configuration
 * instances when they are changed. Objects are registered at {@code Configuration} objects as event listeners and thus
 * can trigger save operations whenever a change event is received.
 * </p>
 * <p>
 * There is one complication however: Some configuration implementations fire change events during a load operation.
 * Such events must be ignored to prevent corruption of the source file. This is achieved by monitoring the associated
 * {@code FileHandler}: during load operations no auto-save is performed.
 * </p>
 *
 * @since 2.0
 */
final class AutoSaveListener extends FileHandlerListenerAdapter implements EventListener<ConfigurationEvent> {
    /** The logger. */
    private final Log log = LogFactory.getLog(getClass());

    /** The associated builder. */
    private final FileBasedConfigurationBuilder<?> builder;

    /** Stores the file handler monitored by this listener. */
    private FileHandler handler;

    /**
     * A counter to keep track whether a load operation is currently in progress.
     */
    private int loading;

    /**
     * Creates a new instance of {@code AutoSaveListener} and initializes it with the associated builder.
     *
     * @param bldr the associated builder
     */
    public AutoSaveListener(final FileBasedConfigurationBuilder<?> bldr) {
        builder = bldr;
    }

    /**
     * Checks whether an auto save operation has to be performed based on the passed in event and the current state of this
     * object.
     *
     * @param event the configuration change event
     * @return <b>true</b> if a save operation should be performed, <b>false</b> otherwise
     */
    private boolean autoSaveRequired(final ConfigurationEvent event) {
        return !event.isBeforeUpdate() && !inLoadOperation();
    }

    /**
     * Returns a flag whether a load operation is currently in progress.
     *
     * @return a flag whether a load operation is in progress
     */
    private synchronized boolean inLoadOperation() {
        return loading > 0;
    }

    /**
     * {@inheritDoc} This implementation decrements the counter for load operations in progress.
     */
    @Override
    public synchronized void loaded(final FileHandler handler) {
        loading--;
    }

    /**
     * {@inheritDoc} This implementation increments the counter for load operations in progress.
     */
    @Override
    public synchronized void loading(final FileHandler handler) {
        loading++;
    }

    /**
     * {@inheritDoc} This implementation checks whether an auto-safe operation should be performed. This is the case if the
     * event indicates that an update of the configuration has been performed and currently no load operation is in
     * progress.
     */
    @Override
    public void onEvent(final ConfigurationEvent event) {
        if (autoSaveRequired(event)) {
            try {
                builder.save();
            } catch (final ConfigurationException ce) {
                log.warn("Auto save failed!", ce);
            }
        }
    }

    /**
     * Updates the {@code FileHandler}. This method is called by the builder when a new configuration instance was created
     * which is associated with a new file handler. It updates the internal file handler reference and performs necessary
     * listener registrations.
     *
     * @param fh the new {@code FileHandler} (can be <b>null</b>)
     */
    public synchronized void updateFileHandler(final FileHandler fh) {
        if (handler != null) {
            handler.removeFileHandlerListener(this);
        }

        if (fh != null) {
            fh.addFileHandlerListener(this);
        }
        handler = fh;
    }
}