Package org.objectweb.joram.mom.dest

Source Code of org.objectweb.joram.mom.dest.Topic

/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2001 - 2010 ScalAgent Distributed Technologies
* Copyright (C) 2003 - 2004 Bull SA
* Copyright (C) 1996 - 2000 Dyade
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
* USA.
*
* Initial developer(s): Frederic Maistre (INRIA)
* Contributor(s): ScalAgent Distributed Technologies
*/
package org.objectweb.joram.mom.dest;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.objectweb.joram.mom.notifications.AbstractRequestNot;
import org.objectweb.joram.mom.notifications.ClientMessages;
import org.objectweb.joram.mom.notifications.ClusterJoinAck;
import org.objectweb.joram.mom.notifications.ClusterJoinNot;
import org.objectweb.joram.mom.notifications.ClusterRemoveNot;
import org.objectweb.joram.mom.notifications.ExceptionReply;
import org.objectweb.joram.mom.notifications.FwdAdminRequestNot;
import org.objectweb.joram.mom.notifications.SubscribeReply;
import org.objectweb.joram.mom.notifications.SubscribeRequest;
import org.objectweb.joram.mom.notifications.TopicForwardNot;
import org.objectweb.joram.mom.notifications.TopicMsgsReply;
import org.objectweb.joram.mom.notifications.UnsubscribeRequest;
import org.objectweb.joram.mom.notifications.WakeUpNot;
import org.objectweb.joram.mom.util.DMQManager;
import org.objectweb.joram.shared.DestinationConstants;
import org.objectweb.joram.shared.MessageErrorConstants;
import org.objectweb.joram.shared.admin.AdminReply;
import org.objectweb.joram.shared.admin.AdminRequest;
import org.objectweb.joram.shared.admin.ClusterAdd;
import org.objectweb.joram.shared.admin.ClusterLeave;
import org.objectweb.joram.shared.admin.ClusterList;
import org.objectweb.joram.shared.admin.ClusterListReply;
import org.objectweb.joram.shared.admin.GetDMQSettingsReply;
import org.objectweb.joram.shared.admin.GetDMQSettingsRequest;
import org.objectweb.joram.shared.admin.GetFatherReply;
import org.objectweb.joram.shared.admin.GetFatherRequest;
import org.objectweb.joram.shared.admin.GetNumberReply;
import org.objectweb.joram.shared.admin.GetSubscriberIds;
import org.objectweb.joram.shared.admin.GetSubscriberIdsRep;
import org.objectweb.joram.shared.admin.GetSubscriptionsRequest;
import org.objectweb.joram.shared.admin.SetFather;
import org.objectweb.joram.shared.excepts.AccessException;
import org.objectweb.joram.shared.excepts.MomException;
import org.objectweb.joram.shared.messages.Message;
import org.objectweb.joram.shared.selectors.Selector;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.agent.AgentId;
import fr.dyade.aaa.agent.AgentServer;
import fr.dyade.aaa.agent.Channel;
import fr.dyade.aaa.agent.DeleteNot;
import fr.dyade.aaa.agent.Notification;
import fr.dyade.aaa.agent.UnknownAgent;
import fr.dyade.aaa.common.Debug;

/**
* The <code>Topic</code> class implements the MOM topic behavior,
* basically distributing the received messages to subscribers.
* <p>
* A Topic might be part of a hierarchy; if it is the case, and if the topic is
* not on top of that hierarchy, it will have a father to forward messages to.
* <p>
* A topic might also be part of a cluster; if it is the case, it will have
* friends to forward messages to.
* <p>
* A topic can be part of a hierarchy and of a cluster at the same time.
*/
public class Topic extends Destination implements TopicMBean {

  public static Logger logger = Debug.getLogger(Topic.class.getName());

  /** define serialVersionUID for interoperability */
  private static final long serialVersionUID = 1L;

  /** Identifier of this topic's father, if any. */
  protected AgentId fatherId = null;

  /** Set of cluster elements (including itself), if any. */
  protected Set friends = null;

  /** Vector of subscribers' identifiers. */
  protected List subscribers = new Vector();

  /** Table of subscribers' selectors. */
  protected Map selectors = new Hashtable();

  /** Internal boolean used for tagging local sendings. */
  protected transient boolean alreadySentLocally;

  public Topic() {
  }

  public Topic(String string, boolean b, int joramAdminStamp) {
    super(string, b, joramAdminStamp);
  }

  public final byte getType() {
    return DestinationConstants.TOPIC_TYPE;
  }

  /**
   * Distributes the received notifications to the appropriate reactions.
   *
   * @throws Exception
   */
  public void react(AgentId from, Notification not) throws Exception {
    setAlreadySentLocally(false);
    int reqId = -1;
    if (not instanceof AbstractRequestNot)
      reqId = ((AbstractRequestNot) not).getRequestId();

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "--- " + this + ": got " + not.getClass().getName() + " with id: " + reqId
          + " from: " + from.toString());
    try {
      if (not instanceof ClusterJoinAck)
        clusterJoinAck((ClusterJoinAck) not);
      else if (not instanceof ClusterJoinNot)
        clusterJoin((ClusterJoinNot) not);
      else if (not instanceof ClusterRemoveNot)
        clusterRemove(from);
      else if (not instanceof SubscribeRequest)
        subscribeRequest(from, (SubscribeRequest) not);
      else if (not instanceof UnsubscribeRequest)
        unsubscribeRequest(from);
      else if (not instanceof TopicForwardNot)
        topicForwardNot(from, (TopicForwardNot) not);
      else
        super.react(from, not);
    } catch (MomException exc) {
      // MOM exceptions are sent to the requester.
      if (logger.isLoggable(BasicLevel.WARN))
        logger.log(BasicLevel.WARN, exc);

      AbstractRequestNot req = (AbstractRequestNot) not;
      Channel.sendTo(from, new ExceptionReply(req, exc));
    }
  }

  /**
   * Initializes the destination.
   *
   * @param firstTime    true when first called by the factory
   */
  public void initialize(boolean firstTime) {
  }

  /**
   * Returns a string representation of this destination.
   */
  public String toString() {
    return "Topic:" + getId().toString();
  }

  public void wakeUpNot(WakeUpNot not) {
    // nothing to do
  }

  /**
   * Reaction to the request of adding a new cluster element.
   */
  private void clusterAdd(FwdAdminRequestNot req, String joiningTopic) {
    AgentId newFriendId = AgentId.fromString(joiningTopic);

    // Adds the given topic to the cluster containing the current one.
    if (friends == null) {
      friends = new HashSet();
      friends.add(getId());
    }
    forward(newFriendId,
        new ClusterJoinNot(friends, req.getReplyTo(), req.getRequestMsgId(), req.getReplyMsgId()));
  }

  /**
   * Method implementing the reaction to a {@link ClusterJoinNot} notification,
   * sent by a fellow topic for notifying this topic to join the cluster, doing
   * a transitive closure of clusters, if any.
   */
  protected void clusterJoin(ClusterJoinNot not) {
    setSave(); // state change, so save.
    if (friends == null) {
      friends = new HashSet();
      friends.add(getId());
    }
    friends.addAll(not.getCluster());
    sendToCluster(new ClusterJoinAck(friends));
    replyToTopic(new AdminReply(true, null), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
  }

  /**
   * Method implementing the reaction to a {@link ClusterJoinAck} notification,
   * doing a transitive closure with the current cluster and the one of the new
   * cluster element.
   */
  protected void clusterJoinAck(ClusterJoinAck not) {
    friends.addAll(not.getCluster());

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "--- " + this + " Topic.clusterJoinAck(" + not + ")" + "\nfriends="
          + friends);
  }

  /**
   * Returns the cluster list.
   *
   * @return the cluster list.
   */
  private List clusterList() {
    List cluster = new ArrayList();
    if (friends != null) {
      Iterator iterator = friends.iterator();
      while (iterator.hasNext()) {
        cluster.add(iterator.next().toString());
      }
    } else {
      cluster.add(getAgentId());
    }
    return cluster;
  }

  public String[] getClusterElements() {
    List list = clusterList();
    return (String[]) list.toArray(new String[list.size()]);
  }

  /**
   * Ask this topic to leave the cluster.
   */
  private void clusterLeave() {
    // Removes this topic of its cluster
    if (friends != null) {
      // Sends a notification to all members asking to remove the topic
      sendToCluster(new ClusterRemoveNot());
      friends = null;
      setSave(); // state change, so save.
    }
  }

  /**
   * Remove the specified topic from current cluster.
   *
   * @param topic The topic which left the cluster
   */
  private void clusterRemove(AgentId topic) {
    setSave(); // state change, so save.
    friends.remove(topic);
    if (friends.size() == 1)
      friends = null;
  }

  /**
   * Sends a notification to all topics in cluster.
   *
   * @param not The notification to send.
   */
  protected void sendToCluster(Notification not) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "--- " + this + " Topic.sendToCluster(" + not + ")");

    if (friends == null || friends.size() < 2)
      return;

    for (Iterator e = friends.iterator(); e.hasNext();) {
      AgentId id = (AgentId) e.next();
      if (!id.equals(getId()))
        forward(id, not);
    }
  }

  /**
   * This method is currently needed by the JMSBridge.
   * AF(TODO): Remove it.
   * @deprecated
   */
  public void preSubscribe() {}
  /**
   * This method is currently needed by the JMSBridge.
   * AF(TODO): Remove it.
   * @deprecated
   */
  public void postSubscribe() {}

  /**
   * Method implementing the reaction to a <code>SubscribeRequest</code>
   * instance.
   *
   * @exception AccessException  If the sender is not a READER.
   */
  protected void subscribeRequest(AgentId from, SubscribeRequest not) throws AccessException {
    if (! isReader(from))
      throw new AccessException("READ right not granted");

    preSubscribe();

    setSave(); // state change, so save.
   
    // Adding new subscriber.
    if (! subscribers.contains(from)) {
      subscribers.add(from);
    }

    // The requester might either be a new subscriber, or an existing one;
    // setting the selector, possibly by removing or modifying an already set
    // expression.
    if (not.getSelector() != null && ! not.getSelector().equals(""))
      selectors.put(from, not.getSelector());
    else
      selectors.remove(from);

    if (!not.isAsyncSub())
      forward(from, new SubscribeReply(not));

    postSubscribe();

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 "Client " + from + " set as a subscriber with selector " + not.getSelector());
  }

  /**
   * This method is currently needed by the JMSBridge.
   * AF(TODO): Remove it.
   * @deprecated
   */
  public void preUnsubscribe() {}
  /**
   * This method is currently needed by the JMSBridge.
   * AF(TODO): Remove it.
   * @deprecated
   */
  public void postUnsubscribe() {}

  /**
   * Method implementing the reaction to an <code>UnsubscribeRequest</code>
   * instance, requesting to remove a subscriber.
   */
  protected void unsubscribeRequest(AgentId from) {
    preUnsubscribe();

    setSave(); // state change, so save.
    subscribers.remove(from);
    selectors.remove(from);

    postUnsubscribe();

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,  "Client " + from + " removed from the subscribers.");
  }

  /**
   * Method implementing the reaction to a <code>TopicForwardNot</code>
   * instance, carrying messages forwarded by a cluster fellow or a
   * hierarchical son.
   */
  protected void topicForwardNot(AgentId from, TopicForwardNot not) {
    doClientMessages(from, not.messages, not.fromCluster);
  }

  /**
   *
   * @see org.objectweb.joram.mom.dest.Destination#handleAdminRequestNot(fr.dyade.aaa.agent.AgentId, org.objectweb.joram.mom.notifications.FwdAdminRequestNot)
   */
  public void handleAdminRequestNot(AgentId from, FwdAdminRequestNot not) {
    AdminRequest adminRequest = not.getRequest();
   
    if (adminRequest instanceof GetSubscriberIds) {
      replyToTopic(new GetSubscriberIdsRep(getSubscriberIds()),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetSubscriptionsRequest) {
      replyToTopic(new GetNumberReply(getNumberOfSubscribers()),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetDMQSettingsRequest) {
      replyToTopic(new GetDMQSettingsReply((dmqId != null)?dmqId.toString():null, 0),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof SetFather) {
      setSave(); // state change, so save.
      this.fatherId = AgentId.fromString(((SetFather) adminRequest).getFather());
      replyToTopic(new AdminReply(true, null),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetFatherRequest) {
      replyToTopic(new GetFatherReply((fatherId != null)?fatherId.toString():null),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof ClusterList) {
      List clstr = clusterList();
      replyToTopic(new ClusterListReply(clstr), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof ClusterAdd) {
      clusterAdd(not, ((ClusterAdd) adminRequest).getAddedDest());
    } else if (adminRequest instanceof ClusterLeave) {
      clusterLeave();
      replyToTopic(new AdminReply(true, null), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else {
      super.handleAdminRequestNot(from, not);
    }
  }

  /**
   * Returns the number of subscribers.
   * Each user appears once even if there is multiples subscriptions, the different
   * subscriptions can be enumerate through the proxy MBean.
   *
   * @return the number of subscribers.
   */
  public final int getNumberOfSubscribers() {
    return subscribers.size();
  }

  /**
   * Returns the list of unique identifiers of all subscribers. Each user
   * appears once even if there is multiples subscriptions, the different
   * subscriptions can be enumerate through the proxy MBean.
   *
   * @return the list of unique identifiers of all subscribers.
   */
  public final String[] getSubscriberIds() {
    String[] res = new String[subscribers.size()];
    for (int i = 0; i < res.length; i++) {
      AgentId aid = (AgentId) subscribers.get(i);
      res[i] = aid.toString();
    }
    return res;
  }

  /**
   * Method specifically processing a <code>SetRightRequest</code> instance.
   * <p>
   * When a reader is removed, deleting this reader's subscription if any,
   * and sending an <code>ExceptionReply</code> notification to the client.
   */
  protected void doRightRequest(AgentId user, int right) {
    // If the request does not unset a reader, doing nothing.
    if (right != -READ) return;

    if (user != null) {
      // Identified user: removing it.
      setSave(); // state change, so save.
      subscribers.remove(user);
      selectors.remove(user);
      forward(user, new ExceptionReply(new AccessException("READ right removed.")));
    } else {
      // Free reading right removed: removing all non readers.
      for (Iterator subs = subscribers.iterator(); subs.hasNext();) {
        user = (AgentId) subs.next();
        if (! isReader(user)) {
          setSave(); // state change, so save.
          subs.remove();
          selectors.remove(user);
          forward(user, new ExceptionReply(new AccessException("READ right removed.")));
        }
      }
    }
  }

  /**
   * Method specifically processing a <code>ClientMessages</code> instance.
   * <p>
   * This method may forward the messages to the topic father if any, or
   * to the cluster fellows if any.It may finally send
   * <code>TopicMsgsReply</code> instances to the valid subscribers.
   */
  protected void doClientMessages(AgentId from, ClientMessages not) {
    doClientMessages(from, not, false);
  }

  private void doClientMessages(AgentId from, ClientMessages not, boolean fromCluster) {
    ClientMessages clientMsgs = preProcess(from, not);
    if (clientMsgs != null) {
      // Forwarding the messages to the father or the cluster fellows, if any:
      forwardMessages(clientMsgs, fromCluster);

      // Processing the messages:
      processMessages(clientMsgs);

      postProcess(clientMsgs);
    }
  }

  /**
   * Method specifically processing an <code>UnknownAgent</code> instance.
   * <p>
   * This method notifies the administrator of the failing cluster or
   * hierarchy building request, if needed, or removes the subscriptions of
   * the deleted client, if any, or sets the father identifier to null if it
   * comes from a deleted father.
   */
  protected void doUnknownAgent(UnknownAgent uA) {
    AgentId agId = uA.agent;
    Notification not = uA.not;

    if (not instanceof ClusterJoinNot) {
      ClusterJoinNot cT = (ClusterJoinNot) not;
      logger.log(BasicLevel.ERROR, "Cluster join failed: " + uA.agent + " unknown.");
      String info = "Cluster join failed: Unknown destination.";
      replyToTopic(new AdminReply(AdminReply.BAD_CLUSTER_REQUEST, info), cT.getReplyTo(),
          cT.getRequestMsgId(), cT.getReplyMsgId());
    } else if (not instanceof ClusterJoinAck || not instanceof ClusterRemoveNot) {
      logger.log(BasicLevel.ERROR, "Cluster error: " + uA.agent + " unknown. "
          + "The topic has probably been removed in the meantime.");
      clusterRemove(agId);
    } else {
      setSave(); // state change, so save.
      // Removing the deleted client's subscriptions, if any.
      subscribers.remove(agId);
      selectors.remove(agId);

      // Removing the father identifier, if needed.
      if (agId.equals(fatherId))
        fatherId = null;
    }
  }

  /**
   * Method specifically processing a <code>fr.dyade.aaa.agent.DeleteNot</code>
   * instance.
   * <p>
   * <code>UnknownAgent</code> notifications are sent to each subscriber and
   * <code>UnclusterNot</code> notifications to the cluster fellows.
   */
  protected void doDeleteNot(DeleteNot not) {
    AgentId clientId;

    // For each subscriber...
    for (int i = 0; i < subscribers.size(); i++) {
      clientId = (AgentId) subscribers.get(i);
      forward(clientId, new UnknownAgent(getId(), null));
    }

    clusterLeave();
    setSave(); // state change, so save.
  }

  /**
   * Actually forwards a list of messages to the father or the cluster
   * fellows, if any.
   */
  protected void forwardMessages(ClientMessages messages) {
    forwardMessages(messages, false);
  }

  private void forwardMessages(ClientMessages messages, boolean fromCluster) {
    if (!fromCluster) {
      sendToCluster(new TopicForwardNot(messages, true));
    }
    if (fatherId != null) {
      forward(fatherId, new TopicForwardNot(messages, false));

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "Messages forwarded to father " + fatherId.toString());
    }
  }

  /**
   * Actually processes the distribution of the received messages to the
   * valid subscriptions by sending a <code>TopicMsgsReply</code> notification
   * to the valid subscribers.
   */
  protected void processMessages(ClientMessages not) {
    List messages = not.getMessages();
    AgentId subscriber;
    boolean local;
    String selector;
    List deliverables;
    Message message;

    nbMsgsReceiveSinceCreation = nbMsgsReceiveSinceCreation + messages.size();
   
    // interceptors process
    if (interceptorsAvailable()) {
      DMQManager dmqManager = null;
      List newMessages = new ArrayList();
      Iterator it = messages.iterator();
      while (it.hasNext()) {
        Message m = (Message) it.next();
        // set the destination name to the message
        m.setProperty("JoramDestinationName", getName());
        message = processInterceptors(m);
        if (message != null) {
          newMessages.add(message);
        } else {
          //send message to the DMQ
          if (dmqManager == null)
            dmqManager = new DMQManager(dmqId, getId());
          nbMsgsSentToDMQSinceCreation++;
          dmqManager.addDeadMessage(m, MessageErrorConstants.INTERCEPTORS);
        }
      }
     
      if (dmqManager != null)
        dmqManager.sendToDMQ();
     
      if (!newMessages.isEmpty()) {
        messages = newMessages;
      } else {
        return;
      }
    }
   
    setNoSave();
    boolean persistent = false;

    for (Iterator subs = subscribers.iterator(); subs.hasNext();) {
      // Browsing the subscribers.
      subscriber = (AgentId) subs.next();
      local = (subscriber.getTo() == AgentServer.getServerId());
      selector = (String) selectors.get(subscriber);

      if (selector == null || selector.equals("")) {
        // Current subscriber does not filter messages: all messages
        // will be sent.
        if (! local) {
          // Subscriber not local, or no other sending occurred locally:
          // directly sending the messages.
          deliverables = messages;
          persistent = true;
        } else if (! alreadySentLocally) {
          deliverables = messages;
          alreadySentLocally = true;
        }
        // A local sending already occurred: cloning the messages.
        else {
          deliverables = new Vector();
          for (Iterator msgs = messages.iterator(); msgs.hasNext();)
            deliverables.add(((Message) msgs.next()).clone());
        }
      } else {
        // Current subscriber filters messages; sending the matching messages.
        deliverables = new Vector();
        for (int i = 0; i < messages.size(); i++) {
          message = (Message) messages.get(i);

          if (Selector.matches(message, selector)) {

            // Subscriber not local, or no other sending occurred locally:
            // directly sending the message.
            if (! local) {
              deliverables.add(message);
              persistent = true;
            } else if (! alreadySentLocally) {
              deliverables.add(message);
              alreadySentLocally = true;
            }
            // A local sending already occurred: cloning the message.
            else
              deliverables.add(message.clone());
          }
        } 
      }
      // There are messages to send.
      if (! deliverables.isEmpty()) {
        TopicMsgsReply topicMsgsReply = new TopicMsgsReply(deliverables);
        topicMsgsReply.setPersistent(persistent);
        setDmq(topicMsgsReply);
        forward(subscriber, topicMsgsReply);
        nbMsgsDeliverSinceCreation = nbMsgsDeliverSinceCreation + deliverables.size();
      }
    }
  }

  private void setDmq(TopicMsgsReply not) {
    // Setting the producer's DMQ identifier field:
    if (dmqId != null) {
      not.setDMQId(dmqId);
    } else {
      not.setDMQId(Queue.getDefaultDMQId());
    }
  }

  // AF (TODO): This method seems to be useless, verify and delete it.
  public void setAlreadySentLocally(boolean alreadySentLocally) {
    this.alreadySentLocally = alreadySentLocally;
  }

  public long getNbMsgsReceiveSinceCreation() {
    return nbMsgsReceiveSinceCreation;
  }
}
TOP

Related Classes of org.objectweb.joram.mom.dest.Topic

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.