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

org.lable.oss.dynamicconfig.provider.zookeeper.ZookeepersAsConfigSource Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
/**
 * Copyright (C) 2015 Lable ([email protected])
 *
 * 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 org.lable.oss.dynamicconfig.provider.zookeeper;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.lable.oss.dynamicconfig.core.ConfigChangeListener;
import org.lable.oss.dynamicconfig.core.ConfigurationException;
import org.lable.oss.dynamicconfig.core.ConfigurationInitializer;
import org.lable.oss.dynamicconfig.core.spi.ConfigurationSource;
import org.lable.oss.dynamicconfig.core.spi.HierarchicalConfigurationDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.zookeeper.AsyncCallback.DataCallback;
import static org.apache.zookeeper.Watcher.Event.KeeperState;

/**
 * Retrieve configuration from a Zookeeper quorum, and maintain a watch for updates.
 */
public class ZookeepersAsConfigSource implements ConfigurationSource {
    private final static Logger logger = LoggerFactory.getLogger(ZookeepersAsConfigSource.class);

    /**
     * Wait this long, in seconds, before the connection attempt to the Zookeeper quorum should time out.
     */
    static int ZOOKEEPER_TIMEOUT = 10;

    String[] quorum;
    String znode;
    String copyQuorumTo;

    NodeWatcher watcher;
    ExecutorService executorService;

    /**
     * Construct a new ZookeepersAsConfigSource.
     */
    public ZookeepersAsConfigSource() {
        // Intentionally empty.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String name() {
        return "zookeeper";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List systemProperties() {
        return Arrays.asList("quorum", "znode", "copy.quorum.to");
    }

    /**
     * {@inheritDoc}
     * 

* This class required two parameters to be set in the configuration object passed: *

*
quorum *
Comma-separated list of addresses for the Zookeeper quorum. *
znode *
Path to the configuration node. *
* Additionally, the following optional parameter may be set: *
*
copy.quorum.to *
Copy the ZooKeeper quorum to this configuration parameter to make it available in the configuration * object loaded by this class. *
*/ @Override public void configure(Configuration configuration) throws ConfigurationException { String[] quorum = configuration.getStringArray("quorum"); String znode = configuration.getString("znode"); String copyQuorumTo = configuration.getString("copy.quorum.to"); String appName = configuration.getString(ConfigurationInitializer.APPNAME_PROPERTY); if (quorum.length == 0) { throw new ConfigurationException("quorum", "No ZooKeeper quorum specified."); } if (isBlank(znode)) { throw new ConfigurationException("znode", "No znode specified."); } if (isBlank(appName)) { throw new ConfigurationException(ConfigurationInitializer.APPNAME_PROPERTY, "No application name found."); } if (isNotBlank(copyQuorumTo)) { this.copyQuorumTo = copyQuorumTo; } this.quorum = quorum; this.znode = combinePath(znode, appName); } /** * {@inheritDoc} *

* When this method is called, it connects to the Zookeeper quorum, and maintains a watch on the * configuration node. */ @Override public void listen(final HierarchicalConfigurationDeserializer deserializer, final ConfigChangeListener listener) { // Act on changed data. DataCallback callback = new DataCallback() { @Override public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) { if (stat != null) { HierarchicalConfiguration hc = null; try { hc = parseData(deserializer, data); } catch (ConfigurationException e) { logger.error("Received invalid data from ZooKeeper quorum. I am ignoring it and keeping the " + "current configuration!", e); return; } if (hc != null) { logger.info("Configuration received from Zookeeper quorum. Znode: " + path); listener.changed(hc); } } } }; // Launch the node watcher. NodeWatcher watcher = new NodeWatcher(StringUtils.join(quorum, ","), callback, znode); this.watcher = watcher; executorService = Executors.newSingleThreadExecutor(); executorService.execute(watcher); } /** * {@inheritDoc} *

* For the initial loading of the configuration, this class connects to the Zookeeper quorum, * waits for a successful connection, and then loads the configuration once. */ @Override public void load(final HierarchicalConfigurationDeserializer deserializer, final ConfigChangeListener listener) throws ConfigurationException { /* It's the */ final CountDownLatch latch = new CountDownLatch(1); ZooKeeper zookeeper; // Connect to the quorum and wait for the successful connection callback.; try { zookeeper = new ZooKeeper(StringUtils.join(quorum, ","), ZOOKEEPER_TIMEOUT * 1000, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { if (watchedEvent.getState() == KeeperState.SyncConnected) { // Signal that the Zookeeper connection is established. latch.countDown(); } } }); } catch (IOException e) { throw new ConfigurationException("Failed to open a connection to the Zookeeper quorum: " + StringUtils.join(quorum, ","), e); } // Retrieve the configuration data. byte[] configData; try { // Wait for the connection to be established. boolean successfulCountDown = latch.await(ZOOKEEPER_TIMEOUT * 1000 + 300, TimeUnit.MILLISECONDS); if (!successfulCountDown) { // The latch timed out. This means a connection to the quorum could not be established. throw new ConfigurationException("Zookeeper connection attempt timed out."); } logger.info("Looking at " + znode + " for configuration data."); configData = zookeeper.getData(znode, false, null); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new ConfigurationException("Thread interrupted."); } catch (KeeperException e) { throw new ConfigurationException("Problem accessing configuration data through Zookeeper quorum.", e); } catch (IllegalArgumentException e) { throw new ConfigurationException("Path to configuration data in Zookeeper quorum does not make sense: " + znode, e); } finally { try { zookeeper.close(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } HierarchicalConfiguration hc = parseData(deserializer, configData); listener.changed(hc); } /** * {@inheritDoc} */ @Override public void close() throws IOException { watcher.close(); executorService.shutdownNow(); try { executorService.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Parse the raw configuration data. This logs the data if parsing fails. * * @param raw Raw byte data. * @return The configuration tree, or null when parsing fails. */ HierarchicalConfiguration parseData(final HierarchicalConfigurationDeserializer deserializer, byte[] raw) throws ConfigurationException { InputStream bis = new ByteArrayInputStream(raw); HierarchicalConfiguration hc; try { hc = deserializer.deserialize(bis); } catch (ConfigurationException e) { throw new ConfigurationException("Failed to parse configuration data retrieved from Zookeeper quorum. " + "Raw data:\n\n" + new String(raw) + "\n\n" + e.getCause().getMessage()); } // If enabled, copy the Zookeeper quorum to the configuration tree. if (copyQuorumTo != null) { hc.setProperty(copyQuorumTo, quorum); } return hc; } static String combinePath(String znode, String appName) { if (!isBlank(appName)) { if (!znode.substring(znode.length() - 1, znode.length()).equals("/")) { znode += "/"; } if (appName.startsWith("/")) { appName = appName.substring(1); } znode += appName; } return znode; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy