Package org.cometd.oort

Source Code of org.cometd.oort.OortList

/*
* Copyright (c) 2008-2014 the original author or authors.
*
* 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.cometd.oort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import org.cometd.bayeux.MarkedReference;
import org.cometd.bayeux.server.BayeuxServer;

/**
* <p>A specialized oort object whose entity is a {@link List}.</p>
* <p>{@link OortList} specializes {@code OortObject} and allows optimized replication of elements
* across the cluster: instead of replicating the whole list, that may be contain a lot of elements,
* only elements that are added or removed are replicated.</p>
* <p>Applications can use {@link #addAndShare(Object[])} and {@link #removeAndShare(Object[])}
* to broadcast changes related to elements, as well as {@link #setAndShare(Object)} to
* change the whole list.</p>
* <p>When one or more elements are changed, {@link ElementListener}s are notified.
* {@link DeltaListener} converts whole list updates triggered by {@link #setAndShare(Object)}
* into events for {@link ElementListener}s, giving applications a single listener type to implement
* their business logic.</p>
*
* @param <E> the element type
*/
public class OortList<E> extends OortObject<List<E>>
{
    private static final String TYPE_FIELD_ELEMENT_VALUE = "oort.list.element";
    private static final String ACTION_FIELD_ADD_VALUE = "oort.list.add";
    private static final String ACTION_FIELD_REMOVE_VALUE = "oort.list.remove";

    private final List<ElementListener<E>> listeners = new CopyOnWriteArrayList<>();

    public OortList(Oort oort, String name, Factory<List<E>> factory)
    {
        super(oort, name, factory);
    }

    public void addElementListener(ElementListener<E> listener)
    {
        listeners.add(listener);
    }

    public void removeElementListener(ElementListener<E> listener)
    {
        listeners.remove(listener);
    }

    /**
     * Returns whether the given {@code element} is present in the local entity list of this node.
     * Differently from {@link #isPresent(Object)}, only the local entity list is scanned.
     *
     * @param element the element to test for presence
     * @return true if the {@code element} is contained in the local entity list, false otherwise
     */
    public boolean contains(E element)
    {
        return getInfo(getOort().getURL()).getObject().contains(element);
    }

    /**
     * Returns whether the given {@code element} is present in one of the entity lists of all nodes.
     * Differently from {@link #contains(Object)} entity lists of all nodes are scanned.
     *
     * @param element the element to test for presence
     * @return true if the {@code element} is contained in one of the entity lists of all nodes, false otherwise
     */
    public boolean isPresent(E element)
    {
        for (Info<List<E>> info : this)
        {
            if (info.getObject().contains(element))
                return true;
        }
        return false;
    }

    /**
     * <p>Adds the given {@code elements} to the local entity list,
     * and then broadcasts the addition to all nodes in the cluster.</p>
     * <p>Calling this method triggers notifications {@link ElementListener}s,
     * both on this node and on remote nodes.</p>
     *
     * @param elements the elements to add
     * @return whether at least one of the elements was added to the local entity list
     */
    public boolean addAndShare(E... elements)
    {
        Data<Boolean> data = new Data<Boolean>(6);
        data.put(Info.VERSION_FIELD, nextVersion());
        data.put(Info.OORT_URL_FIELD, getOort().getURL());
        data.put(Info.NAME_FIELD, getName());
        data.put(Info.OBJECT_FIELD, elements);
        data.put(Info.TYPE_FIELD, TYPE_FIELD_ELEMENT_VALUE);
        data.put(Info.ACTION_FIELD, ACTION_FIELD_ADD_VALUE);

        if (logger.isDebugEnabled())
            logger.debug("Sharing list add {}", data);
        BayeuxServer bayeuxServer = getOort().getBayeuxServer();
        bayeuxServer.getChannel(getChannelName()).publish(getLocalSession(), data);

        return data.getResult();
    }

    /**
     * <p>Removes the given {@code elements} to the local entity list,
     * and then broadcasts the removal to all nodes in the cluster.</p>
     * <p>Calling this method triggers notifications {@link ElementListener}s,
     * both on this node and on remote nodes.</p>
     *
     * @param elements the elements to remove
     * @return whether at least one of the elements was removed from the local entity list
     */
    public boolean removeAndShare(E... elements)
    {
        Data<Boolean> data = new Data<Boolean>(6);
        data.put(Info.VERSION_FIELD, nextVersion());
        data.put(Info.OORT_URL_FIELD, getOort().getURL());
        data.put(Info.NAME_FIELD, getName());
        data.put(Info.OBJECT_FIELD, elements);
        data.put(Info.TYPE_FIELD, TYPE_FIELD_ELEMENT_VALUE);
        data.put(Info.ACTION_FIELD, ACTION_FIELD_REMOVE_VALUE);

        if (logger.isDebugEnabled())
            logger.debug("Sharing list remove {}", data);
        BayeuxServer bayeuxServer = getOort().getBayeuxServer();
        bayeuxServer.getChannel(getChannelName()).publish(getLocalSession(), data);

        return data.getResult();
    }

    @Override
    protected void onObject(Map<String, Object> data)
    {
        if (TYPE_FIELD_ELEMENT_VALUE.equals(data.get(Info.TYPE_FIELD)))
        {
            String action = (String)data.get(Info.ACTION_FIELD);
            final boolean remove = ACTION_FIELD_REMOVE_VALUE.equals(action);
            if (!ACTION_FIELD_ADD_VALUE.equals(action) && !remove)
                throw new IllegalArgumentException(action);

            String oortURL = (String)data.get(Info.OORT_URL_FIELD);
            Info<List<E>> info = getInfo(oortURL);
            if (info != null)
            {
                // Retrieve elements
                Object object = data.get(Info.OBJECT_FIELD);
                if (object instanceof Object[])
                    object = Arrays.asList((Object[])object);
                @SuppressWarnings("unchecked")
                final List<E> elements = (List<E>)object;

                // Set the new Info
                Info<List<E>> newInfo = new Info<>(getOort().getURL(), data);
                final List<E> list = info.getObject();
                newInfo.put(Info.OBJECT_FIELD, list);
                final AtomicBoolean result = new AtomicBoolean();
                MarkedReference<Info<List<E>>> old = setInfo(newInfo, new Runnable()
                {
                    public void run()
                    {
                        if (remove)
                            result.set(list.removeAll(elements));
                        else
                            result.set(list.addAll(elements));
                    }
                });

                if (logger.isDebugEnabled())
                    logger.debug("{} {} list {} of {}",
                        old.isMarked() ? "Performed" : "Skipped",
                        newInfo.isLocal() ? "local" : "remote",
                        remove ? "remove" : "add",
                        elements);

                if (old.isMarked())
                {
                    if (remove)
                        notifyElementsRemoved(info, elements);
                    else
                        notifyElementsAdded(info, elements);
                }

                if (data instanceof Data)
                    ((Data<Boolean>)data).setResult(result.get());
            }
            else
            {
                if (logger.isDebugEnabled())
                    logger.debug("No info for {}", oortURL);
            }
        }
        else
        {
            super.onObject(data);
        }
    }

    private void notifyElementsAdded(Info<List<E>> info, List<E> elements)
    {
        for (ElementListener<E> listener : listeners)
        {
            try
            {
                listener.onAdded(info, elements);
            }
            catch (Throwable x)
            {
                logger.info("Exception while invoking listener " + listener, x);
            }
        }
    }

    private void notifyElementsRemoved(Info<List<E>> info, List<E> elements)
    {
        for (ElementListener<E> listener : listeners)
        {
            try
            {
                listener.onRemoved(info, elements);
            }
            catch (Throwable x)
            {
                logger.info("Exception while invoking listener " + listener, x);
            }
        }
    }

    /**
     * Listener for element events that update the entity list, either locally or remotely.
     *
     * @param <E> the element type
     */
    public interface ElementListener<E> extends EventListener
    {
        /**
         * Callback method invoked when elements are added to the entity list.
         *
         * @param info the {@link Info} that was changed by the addition
         * @param elements the elements added
         */
        public void onAdded(Info<List<E>> info, List<E> elements);

        /**
         * Callback method invoked when elements are removed from the entity list.
         *
         * @param info the {@link Info} that was changed by the removal
         * @param elements the elements removed
         */
        public void onRemoved(Info<List<E>> info, List<E> elements);

        /**
         * Empty implementation of {@link ElementListener}.
         *
         * @param <E> the element type
         */
        public static class Adapter<E> implements ElementListener<E>
        {
            public void onAdded(Info<List<E>> info, List<E> elements)
            {
            }

            public void onRemoved(Info<List<E>> info, List<E> elements)
            {
            }
        }
    }

    /**
     * <p>An implementation of {@link Listener} that converts whole list events into {@link ElementListener} events.</p>
     * <p>For example, if an entity list:</p>
     * <pre>
     * [A, B]
     * </pre>
     * <p>is replaced by a list:</p>
     * <pre>
     * [A, C, D]
     * </pre>
     * <p>then this listener generates two "add" events for {@code C} and {@code D}
     * and one "remove" event for {@code B}.</p>
     *
     * @param <E> the element type
     */
    public static class DeltaListener<E> implements Listener<List<E>>
    {
        private final OortList<E> oortList;

        public DeltaListener(OortList<E> oortList)
        {
            this.oortList = oortList;
        }

        public void onUpdated(Info<List<E>> oldInfo, Info<List<E>> newInfo)
        {
            List<E> added = new ArrayList<>(newInfo.getObject());
            added.removeAll(oldInfo.getObject());

            List<E> removed = new ArrayList<>(oldInfo.getObject());
            removed.removeAll(newInfo.getObject());

            if (!added.isEmpty())
                oortList.notifyElementsAdded(newInfo, added);
            if (!removed.isEmpty())
                oortList.notifyElementsRemoved(newInfo, removed);
        }

        public void onRemoved(Info<List<E>> info)
        {
            oortList.notifyElementsRemoved(info, info.getObject());
        }
    }
}
TOP

Related Classes of org.cometd.oort.OortList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.