com.buaa.cfs.conf.ReconfigurableBase.java Source code

Java tutorial

Introduction

Here is the source code for com.buaa.cfs.conf.ReconfigurableBase.java

Source

/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.buaa.cfs.conf;

import com.buaa.cfs.utils.Time;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * Utility base class for implementing the Reconfigurable interface.
 * <p>
 * Subclasses should override reconfigurePropertyImpl to change individual properties and getReconfigurableProperties to
 * get all properties that can be changed at run time.
 */
public abstract class ReconfigurableBase extends Configured implements Reconfigurable {

    private static final Log LOG = LogFactory.getLog(ReconfigurableBase.class);
    // Use for testing purpose.
    private ReconfigurationUtil reconfigurationUtil = new ReconfigurationUtil();

    /** Background thread to reload configuration. */
    private Thread reconfigThread = null;
    private volatile boolean shouldRun = true;
    private Object reconfigLock = new Object();

    /**
     * The timestamp when the <code>reconfigThread</code> starts.
     */
    private long startTime = 0;

    /**
     * The timestamp when the <code>reconfigThread</code> finishes.
     */
    private long endTime = 0;

    /**
     * A map of <changed property, error message>. If error message is present, it contains the messages about the error
     * occurred when applies the particular change. Otherwise, it indicates that the change has been successfully
     * applied.
     */
    private Map<ReconfigurationUtil.PropertyChange, Optional<String>> status = null;

    /**
     * Construct a ReconfigurableBase.
     */
    public ReconfigurableBase() {
        super(new Configuration());
    }

    /**
     * Construct a ReconfigurableBase with the {@link Configuration} conf.
     */
    public ReconfigurableBase(Configuration conf) {
        super((conf == null) ? new Configuration() : conf);
    }

    @VisibleForTesting
    public void setReconfigurationUtil(ReconfigurationUtil ru) {
        reconfigurationUtil = Preconditions.checkNotNull(ru);
    }

    @VisibleForTesting
    public Collection<ReconfigurationUtil.PropertyChange> getChangedProperties(Configuration newConf,
            Configuration oldConf) {
        return reconfigurationUtil.parseChangedProperties(newConf, oldConf);
    }

    /**
     * A background thread to apply configuration changes.
     */
    private static class ReconfigurationThread extends Thread {
        private ReconfigurableBase parent;

        ReconfigurationThread(ReconfigurableBase base) {
            this.parent = base;
        }

        // See {@link ReconfigurationServlet#applyChanges}
        public void run() {
            LOG.info("Starting reconfiguration task.");
            Configuration oldConf = this.parent.getConf();
            Configuration newConf = new Configuration();
            Collection<ReconfigurationUtil.PropertyChange> changes = this.parent.getChangedProperties(newConf,
                    oldConf);
            Map<ReconfigurationUtil.PropertyChange, Optional<String>> results = Maps.newHashMap();
            for (ReconfigurationUtil.PropertyChange change : changes) {
                String errorMessage = null;
                if (!this.parent.isPropertyReconfigurable(change.prop)) {
                    errorMessage = "Property " + change.prop + " is not reconfigurable";
                    LOG.info(errorMessage);
                    results.put(change, Optional.of(errorMessage));
                    continue;
                }
                LOG.info("Change property: " + change.prop + " from \""
                        + ((change.oldVal == null) ? "<default>" : change.oldVal) + "\" to \""
                        + ((change.newVal == null) ? "<default>" : change.newVal) + "\".");
                try {
                    this.parent.reconfigurePropertyImpl(change.prop, change.newVal);
                } catch (ReconfigurationException e) {
                    errorMessage = e.getCause().getMessage();
                }
                results.put(change, Optional.fromNullable(errorMessage));
            }

            synchronized (this.parent.reconfigLock) {
                this.parent.endTime = Time.now();
                this.parent.status = Collections.unmodifiableMap(results);
                this.parent.reconfigThread = null;
            }
        }
    }

    /**
     * Start a reconfiguration task to reload configuration in background.
     */
    public void startReconfigurationTask() throws IOException {
        synchronized (reconfigLock) {
            if (!shouldRun) {
                String errorMessage = "The server is stopped.";
                LOG.warn(errorMessage);
                throw new IOException(errorMessage);
            }
            if (reconfigThread != null) {
                String errorMessage = "Another reconfiguration task is running.";
                LOG.warn(errorMessage);
                throw new IOException(errorMessage);
            }
            reconfigThread = new ReconfigurationThread(this);
            reconfigThread.setDaemon(true);
            reconfigThread.setName("Reconfiguration Task");
            reconfigThread.start();
            startTime = Time.now();
        }
    }

    public ReconfigurationTaskStatus getReconfigurationTaskStatus() {
        synchronized (reconfigLock) {
            if (reconfigThread != null) {
                return new ReconfigurationTaskStatus(startTime, 0, null);
            }
            return new ReconfigurationTaskStatus(startTime, endTime, status);
        }
    }

    public void shutdownReconfigurationTask() {
        Thread tempThread;
        synchronized (reconfigLock) {
            shouldRun = false;
            if (reconfigThread == null) {
                return;
            }
            tempThread = reconfigThread;
            reconfigThread = null;
        }

        try {
            tempThread.join();
        } catch (InterruptedException e) {
        }
    }

    /**
     * {@inheritDoc}
     * <p>
     * This method makes the change to this objects {@link Configuration} and calls reconfigurePropertyImpl to update
     * internal data structures. This method cannot be overridden, subclasses should instead override
     * reconfigureProperty.
     */
    @Override
    public final String reconfigureProperty(String property, String newVal) throws ReconfigurationException {
        if (isPropertyReconfigurable(property)) {
            LOG.info("changing property " + property + " to " + newVal);
            String oldVal;
            synchronized (getConf()) {
                oldVal = getConf().get(property);
                reconfigurePropertyImpl(property, newVal);
                if (newVal != null) {
                    getConf().set(property, newVal);
                } else {
                    getConf().unset(property);
                }
            }
            return oldVal;
        } else {
            throw new ReconfigurationException(property, newVal, getConf().get(property));
        }
    }

    /**
     * {@inheritDoc}
     * <p>
     * Subclasses must override this.
     */
    @Override
    public abstract Collection<String> getReconfigurableProperties();

    /**
     * {@inheritDoc}
     * <p>
     * Subclasses may wish to override this with a more efficient implementation.
     */
    @Override
    public boolean isPropertyReconfigurable(String property) {
        return getReconfigurableProperties().contains(property);
    }

    /**
     * Change a configuration property.
     * <p>
     * Subclasses must override this. This method applies the change to all internal data structures derived from the
     * configuration property that is being changed. If this object owns other Reconfigurable objects
     * reconfigureProperty should be called recursively to make sure that to make sure that the configuration of these
     * objects is updated.
     */
    protected abstract void reconfigurePropertyImpl(String property, String newVal) throws ReconfigurationException;

}