org.apache.niolex.notify.Notify.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.niolex.notify.Notify.java

Source

/**
 * Notify.java
 *
 * Copyright 2012 Niolex, 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 org.apache.niolex.notify;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.apache.niolex.commons.bean.Pair;
import org.apache.niolex.commons.codec.KVBase64Util;
import org.apache.niolex.commons.codec.StringUtil;
import org.apache.niolex.commons.collection.CollectionUtil;
import org.apache.niolex.zookeeper.core.ZKConnector;
import org.apache.niolex.zookeeper.core.ZKListener;

import com.google.common.collect.Maps;

/**
 * The core class of this notify framework. Watch the changes of node data and node properties.
 * User can also change node data and node properties.
 * <br>
 * Users can use this class as:<pre>
 * 1. Global configuration center.
 * 2. Message middle-ware.
 * 3. Signal dispatcher.
 * etc...
 * </pre>
 *
 * @author <a href="mailto:xiejiyun@gmail.com">Xie, Jiyun</a>
 * @version 1.0.5
 * @since 2012-12-27
 */
public class Notify implements ZKListener {

    /**
     * Listen to the changes from Notify.
     *
     * @author <a href="mailto:xiejiyun@foxmail.com">Xie, Jiyun</a>
     * @version 1.0.0
     * @since 2013-12-6
     */
    public static interface Listener {

        /**
         * The data of notify changed.
         *
         * @param data the new data
         */
        public void onDataChange(byte[] data);

        /**
         * One property of notify changed. We will not notify property deletion.
         *
         * @param key the property key
         * @param value the property value
         */
        public void onPropertyChange(byte[] key, byte[] value);

    }

    /**
     * This list must be visited in synchronized block.
     */
    private final List<Listener> list = new ArrayList<Listener>();

    /**
     * Use concurrent one to reduce lock usage.
     */
    private final Map<ByteArray, byte[]> properties = Maps.newConcurrentMap();

    private final ZKConnector zkConn;
    private final String basePath;

    private volatile byte[] data;
    private volatile List<String> children;

    /**
     * Create a new notify under this bash path.
     *
     * @param zkConn the ZK connector.
     * @param basePath the ZK base path of this notify. It's fixed for one notify.
     */
    public Notify(ZKConnector zkConn, String basePath) {
        super();
        this.zkConn = zkConn;
        this.basePath = basePath;
        this.data = zkConn.watchData(basePath, this);
        this.children = Collections.emptyList();
        List<String> list = zkConn.watchChildren(basePath, this);
        this.onChildrenChange(list);
    }

    /**
     * Update the data of this notify.
     *
     * @param data the new data.
     */
    public void updateData(String data) {
        updateData(StringUtil.strToUtf8Byte(data));
    }

    /**
     * Update the data of this notify.
     *
     * @param data the new data.
     */
    public void updateData(byte[] data) {
        this.zkConn.updateNodeData(basePath, data);
        // We will not set the new data to local directly, it will be updated by events.
    }

    /**
     * Fired when the data of this notify changed.
     *
     * @param data the new data
     */
    public synchronized void onDataChange(byte[] data) {
        this.data = data;
        for (Listener li : list) {
            li.onDataChange(data);
        }
    }

    /**
     * Delete the property specified by this key.
     *
     * @param key the property key
     * @return true if deleted, false if not found.
     */
    public boolean deleteProperty(String key) {
        return deleteProperty(StringUtil.strToUtf8Byte(key));
    }

    /**
     * Delete the property specified by this key.
     *
     * @param key the property key
     * @return true if deleted, false if not found.
     */
    public boolean deleteProperty(byte[] key) {
        ByteArray k = new ByteArray(key);
        byte[] v = this.properties.get(k);
        if (v == null) {
            return false;
        }
        // Old property exist, we delete it.
        String p = KVBase64Util.kvToBase64(key, v);
        this.zkConn.deleteNode(basePath + "/" + p);
        this.properties.remove(k);
        return true;
    }

    /**
     * Replace the property specified by this key if found, add a new
     * property for this key otherwise.
     *
     * @param key the key to replace
     * @param value the new value
     */
    public void replaceProperty(String key, String value) {
        replaceProperty(StringUtil.strToUtf8Byte(key), StringUtil.strToUtf8Byte(value));
    }

    /**
     * Replace the property specified by this key if found, add a new
     * property for this key otherwise.
     *
     * @param key the key to replace
     * @param value the new value
     */
    public void replaceProperty(byte[] key, byte[] value) {
        ByteArray k = new ByteArray(key);
        byte[] v = this.properties.get(k);
        if (v != null) {
            if (Arrays.equals(v, value)) {
                return;
            }
            // Old property exist, we delete it.
            String p = KVBase64Util.kvToBase64(key, v);
            this.zkConn.deleteNode(basePath + "/" + p);
        }
        this.properties.put(k, value);
        // The new property path.
        String p = KVBase64Util.kvToBase64(key, value);
        this.zkConn.createNode(basePath + "/" + p);
    }

    /**
     * Add this specified listener.
     *
     * @param listener listener to be added to this notify
     */
    public synchronized void addListener(Listener listener) {
        list.add(listener);
    }

    /**
     * Remote this specified listener.
     *
     * @param listener listener to be removed from this notify, if present
     * @return true if success, false if not found.
     */
    public synchronized boolean removeListener(Listener listener) {
        return list.remove(listener);
    }

    /**
     * Fired when the children of this notify changed.
     *
     * @param list the new children list
     */
    public void onChildrenChange(List<String> list) {
        Pair<List<String>, List<String>> pair = CollectionUtil.intersection(this.children, list);
        for (String s : pair.a) {
            // All the deleted item.
            if (Base64.isBase64(s)) {
                // We try to decode it.
                try {
                    Pair<byte[], byte[]> kv = KVBase64Util.base64toKV(s);
                    properties.remove(new ByteArray(kv.a));
                } catch (Exception e) {
                }
            }
        }
        this.children = list;
        for (String s : pair.b) {
            // All the added item.
            if (Base64.isBase64(s)) {
                // We try to decode it.
                try {
                    Pair<byte[], byte[]> kv = KVBase64Util.base64toKV(s);
                    properties.put(new ByteArray(kv.a), kv.b);
                    firePropertyChange(kv.a, kv.b);
                } catch (Exception e) {
                }
            }
        }
    }

    /**
     * We will not fire property change on property delete.
     *
     * @param a the new property key
     * @param b the new property value
     */
    private synchronized void firePropertyChange(byte[] a, byte[] b) {
        for (Listener li : list) {
            li.onPropertyChange(a, b);
        }
    }

    /**
     * Get the property in this notify.
     *
     * @param key the property key
     * @return the property value, null if not found
     */
    public String getProperty(String key) {
        byte[] v = getProperty(StringUtil.strToUtf8Byte(key));
        return v == null ? null : StringUtil.utf8ByteToStr(v);
    }

    /**
     * Get the property in this notify.
     *
     * @param key the property key
     * @return the property value, null if not found
     */
    public byte[] getProperty(byte[] key) {
        return properties.get(new ByteArray(key));
    }

    /**
     * Get the property in this notify.
     *
     * @param key the property key
     * @return the property value, null if not found
     */
    public byte[] getProperty(ByteArray key) {
        return properties.get(key);
    }

    /**
     * @return the current notify data.
     */
    public byte[] getData() {
        return data;
    }

    /**
     * @return the properties map, it's read-only.
     */
    public Map<ByteArray, byte[]> getProperties() {
        return Collections.unmodifiableMap(properties);
    }

}