Package org.objectweb.joram.mom.dest

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

/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2001 - 2011 ScalAgent Distributed Technologies
* 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.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import org.objectweb.joram.mom.notifications.AbstractRequestNot;
import org.objectweb.joram.mom.notifications.ClientMessages;
import org.objectweb.joram.mom.notifications.ExceptionReply;
import org.objectweb.joram.mom.notifications.FwdAdminRequestNot;
import org.objectweb.joram.mom.notifications.GetRightsReplyNot;
import org.objectweb.joram.mom.notifications.GetRightsRequestNot;
import org.objectweb.joram.mom.notifications.RequestGroupNot;
import org.objectweb.joram.mom.notifications.WakeUpNot;
import org.objectweb.joram.mom.proxies.SendRepliesNot;
import org.objectweb.joram.mom.proxies.SendReplyNot;
import org.objectweb.joram.mom.util.DMQManager;
import org.objectweb.joram.mom.util.InterceptorsHelper;
import org.objectweb.joram.mom.util.MessageInterceptor;
import org.objectweb.joram.shared.MessageErrorConstants;
import org.objectweb.joram.shared.admin.AdminCommandConstant;
import org.objectweb.joram.shared.admin.AdminCommandReply;
import org.objectweb.joram.shared.admin.AdminCommandRequest;
import org.objectweb.joram.shared.admin.AdminReply;
import org.objectweb.joram.shared.admin.AdminRequest;
import org.objectweb.joram.shared.admin.DeleteDestination;
import org.objectweb.joram.shared.admin.GetStatsReply;
import org.objectweb.joram.shared.admin.GetStatsRequest;
import org.objectweb.joram.shared.admin.SetDMQRequest;
import org.objectweb.joram.shared.admin.SetReader;
import org.objectweb.joram.shared.admin.SetRight;
import org.objectweb.joram.shared.admin.SetWriter;
import org.objectweb.joram.shared.admin.UnsetReader;
import org.objectweb.joram.shared.admin.UnsetWriter;
import org.objectweb.joram.shared.excepts.AccessException;
import org.objectweb.joram.shared.excepts.MomException;
import org.objectweb.joram.shared.excepts.RequestException;
import org.objectweb.joram.shared.messages.ConversionHelper;
import org.objectweb.joram.shared.messages.Message;
import org.objectweb.joram.shared.messages.MessageHelper;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.agent.Agent;
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.agent.UnknownNotificationException;
import fr.dyade.aaa.agent.WakeUpTask;
import fr.dyade.aaa.common.Debug;
import fr.dyade.aaa.util.management.MXWrapper;

/**
* The <code>Destination</code> class implements the common behavior of
* MOM destinations.
*/
public abstract class Destination extends Agent implements DestinationMBean {

  public static Logger logger = Debug.getLogger(Destination.class.getName());
 
  public static final String WAKEUP_PERIOD = "period";
 
  /**
   * <code>true</code> if the destination successfully processed a deletion
   * request.
   */
  private boolean deletable = false;

  /** period to run task at regular interval: cleaning, load-balancing, etc. */
  private long period = -1L;
 
  /**
   * Identifier of the destination's administrator.
   * In any case the administration topics are authorized to handle the
   * destination, this mechanism allows an other agent to get the same rights.
   * In particular it is needed to allow user's proxy to handle temporary
   * destinations.
   */
  private AgentId adminId;
 
  protected transient WakeUpTask task;

  /** the interceptors list. */
  private String interceptorsStr = null;
  private transient List interceptors = null;
 
  /**
   * Empty constructor for newInstance().
   */
  public Destination() {
  }

  /**
   * Constructor with parameters for fixing the destination and specifying its
   * identifier.
   * It is uniquely used by the AdminTopic agent.
   */
  protected Destination(String name, boolean fixed, int stamp) {
    super(name, fixed, stamp);
  }

  /**
   * Returns the type of this destination: Queue or Topic.
   *
   * @return the type of this destination.
   * @see org.objectweb.joram.shared.DestinationConstants#TOPIC_TYPE
   * @see org.objectweb.joram.shared.DestinationConstants#QUEUE_TYPE
   */
  public abstract byte getType();

  /**
   * Gives this agent an opportunity to initialize after having been deployed,
   * and each time it is loaded into memory.
   *
   * @param firstTime true when first called by the factory
   * @exception Exception
   *              unspecialized exception
   */
  protected void agentInitialize(boolean firstTime) throws Exception {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "agentInitialize(" + firstTime + ')');

    super.agentInitialize(firstTime);
   
    // interceptors
    if (interceptorsStr != null) {
      interceptors = new ArrayList();
      InterceptorsHelper.addInterceptors(interceptorsStr, interceptors);
    }
   
    initialize(firstTime);

    if (getPeriod() > -1)
      task = new WakeUpTask(getId(), WakeUpNot.class, getPeriod());

    try {
      MXWrapper.registerMBean(this, getMBeanName());
    } catch (Exception exc) {
      logger.log(BasicLevel.ERROR, this + " jmx failed", exc);
    }
  }

  /** Finalizes the agent before it is garbaged. */
  public void agentFinalize(boolean lastTime) {
    if (task != null)
      task.cancel();
    try {
      MXWrapper.unregisterMBean(getMBeanName());
    } catch (Exception exc) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "Destination.agentFinalize", exc);
    }
    super.agentFinalize(lastTime);
  }

  public String getMBeanName() {
    StringBuffer strbuf = new StringBuffer();

    strbuf.append("Joram#").append(AgentServer.getServerId());
    strbuf.append(':');
    strbuf.append("type=Destination,name=").append(getName());

    return strbuf.toString();
  }

  /**
   * Distributes the received notifications to the appropriate reactions.
   *
   * @throws Exception
   */
  public void react(AgentId from, Notification not) throws Exception {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "Destination.react(" + from + ',' + not + ')');

    // set agent no save (this is the default).
    setNoSave();

    try {
      if (not instanceof GetRightsRequestNot)
        getRights(from, (GetRightsRequestNot) not);
      else if (not instanceof ClientMessages)
        clientMessages(from, (ClientMessages) not);
      else if (not instanceof UnknownAgent)
        unknownAgent(from, (UnknownAgent) not);
      else if (not instanceof RequestGroupNot)
        requestGroupNot(from, (RequestGroupNot) not);
      else if (not instanceof DeleteNot) {
        deleteNot(from, (DeleteNot) not);
        if (canBeDeleted()) {
          // A DeleteNot notification is finally processed at the
          // Agent level when its processing went successful in
          // the DestinationItf instance.
          super.react(from, not);
        }
      } else if (not instanceof WakeUpNot) {
        setNoSave();
        if (task == null || ((WakeUpNot) not).update) {
          doSetPeriod(getPeriod());
        }
        if (getPeriod() > 0) {
          wakeUpNot((WakeUpNot) not);
        }
      } else if (not instanceof FwdAdminRequestNot)
        handleAdminRequestNot(from, (FwdAdminRequestNot) not);
      else
        throw new UnknownNotificationException(not.getClass().getName());
    } catch (MomException exc) {
      // MOM Exceptions are sent to the requester.
      if (logger.isLoggable(BasicLevel.WARN))
        logger.log(BasicLevel.WARN, this + ".react()", exc);

      AbstractRequestNot req = (AbstractRequestNot) not;
      Channel.sendTo(from, new ExceptionReply(req, exc));
    } catch (UnknownNotificationException exc) {
      super.react(from, not);
    }
  }

  private void doSetPeriod(long period) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, this + ": setPeriod(" + period + ")." + " -> task " + task);
    if (task == null) {
      task = new WakeUpTask(getId(), WakeUpNot.class, period);
    } else {
      // cancel task
      task.cancel();
      // Schedules the wake up task period.
      if (period > 0)
        task = new WakeUpTask(getId(), WakeUpNot.class, period);
    }
  }
 
  /** <code>true</code> if the READ access is granted to everybody. */
  protected boolean freeReading = false;
  /** <code>true</code> if the WRITE access is granted to everybody. */
  protected boolean freeWriting = false;
  /** Table of the destination readers and writers. */
  protected Hashtable clients = new Hashtable();

  /** READ access value. */
  public static int READ = 1;
  /** WRITE access value. */
  public static int WRITE = 2;
  /** READ and WRITE access value. */
  public static int READWRITE = 3;

  /**
   * Identifier of the dead message queue this destination must send its
   * dead messages to, if any.
   */
  protected AgentId dmqId = null;

  /**
   * Transient <code>StringBuffer</code> used to build message, this buffer
   * is created during agent initialization, then reused during the destination
   * life.
   */
  transient StringBuffer strbuf = new StringBuffer();

  /**
   * date of creation.
   */
  public long creationDate = System.currentTimeMillis();

  protected long nbMsgsReceiveSinceCreation = 0;
  protected long nbMsgsDeliverSinceCreation = 0;
  protected long nbMsgsSentToDMQSinceCreation = 0;

  /**
   * Sets the administrator of the destination.
   *
   * @param adminId Identifier of the administrator of the destination.
   */
  public final void setAdminId(AgentId adminId) {
    this.adminId = adminId;
  }

  /**
   * Sets the configuration of a <code>Destination</code>. Be careful, this is
   * done a first time before {@link #deploy()}, so the agent is serialized and
   * initialized afterwards.<br>
   * After deployment, firstTime argument is set to false.
   *
   * @param prop The initial set of properties.
   */
  public void setProperties(Properties prop, boolean firstTime) throws Exception {
    if (logger.isLoggable(BasicLevel.DEBUG)) {
      logger.log(BasicLevel.DEBUG, this + ", setProperties.");
    }

    long newPeriod = -1;
    if (prop != null && prop.containsKey(WAKEUP_PERIOD)) {
      try {
        newPeriod = ConversionHelper.toLong(prop.get(WAKEUP_PERIOD));
      } catch (Exception e) {
        logger.log(BasicLevel.ERROR, this + ": error setting destination period", e);
      }
    }
    if (firstTime) {
      period = newPeriod;
    } else {
      setPeriod(newPeriod);
    }
   
    interceptorsStr = null;
    interceptors = null;
    if (prop != null && prop.containsKey(AdminCommandConstant.INTERCEPTORS)) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, this + ": setProperties interceptors = " + prop.get(AdminCommandConstant.INTERCEPTORS));
      interceptorsStr = (String) prop.get(AdminCommandConstant.INTERCEPTORS);
    }
    // Interceptors are set the first time in agent initialization
    if (!firstTime) {
      if (interceptorsStr != null) {
        interceptors = new ArrayList();
        InterceptorsHelper.addInterceptors(interceptorsStr, interceptors);
      } else {
        interceptors = null;
      }
    }
  }

  /**
   * Initializes the destination.
   *
   * @param firstTime true when first called by the factory
   */
  protected abstract void initialize(boolean firstTime);

  protected boolean isLocal(AgentId id) {
    return (getId().getTo() == id.getTo());
  }

  /** Returns <code>true</code> if the destination might be deleted. */
  private final boolean canBeDeleted() {
    return deletable;
  }

  /**
   * Returns  the period value of this destination, -1 if not set.
   *
   * @return the period value of this destination; -1 if not set.
   */
  public long getPeriod() {
    return period;
  }

  /**
   * Sets or unsets the period for this destination.
   *
   * @param period The period value to be set or -1 for unsetting previous
   *               value (ignore 0).
   */
  public void setPeriod(long period) {
    if (this.period != period) {
      // Schedule the task.
      WakeUpNot not = new WakeUpNot();
      not.update = true;
      forward(getId(), not);
      this.period = period;
    }
  }
 
  public abstract void wakeUpNot(WakeUpNot not);
   
  /**
   * Method implementing the reaction to a <code>SetRightRequest</code>
   * notification requesting rights to be set for a user.
   *
   * @param not The notification describing the security modifications.
   */
  protected void setRight(SetRight request,
                       AgentId replyTo,
                       String requestMsgId,
                       String replyMsgId) {
    AgentId user = null;
    if (request.getUserProxId() != null)
      user = AgentId.fromString(request.getUserProxId());

    int right = 0;
    if (request instanceof SetReader)
      right = READ;
    else if (request instanceof SetWriter)
      right = WRITE;
    else if (request instanceof UnsetReader)
      right = - READ;
    else if (request instanceof UnsetWriter)
      right = - WRITE;

    try {
      processSetRight(user, right);
      doRightRequest(user, right);

      replyToTopic(new AdminReply(true, null), replyTo, requestMsgId, replyMsgId);
    } catch (RequestException exc) {
      strbuf.append("Request [").append(request.getClass().getName());
      strbuf.append("], sent to Destination [").append(getId());
      strbuf.append("], successful [false]: ").append(exc.getMessage());
      replyToTopic(new AdminReply(false, strbuf.toString()), replyTo, requestMsgId, replyMsgId);
      logger.log(BasicLevel.ERROR, strbuf.toString());
      strbuf.setLength(0);
    }
  }
  /**
   * This method is needed for right revocation.
   * It allows to remove request or subscription from users no longer authorized.
   *
   * @param user  The user about right modification.
   * @param right The right modification.
   */
  abstract protected void doRightRequest(AgentId user, int right);

  /** set user right. */
  protected void processSetRight(AgentId user, int right)
    throws RequestException {
    // state change, so save.
    setSave();

    // Setting "all" users rights:
    if (user == null) {
      if (right == READ)
        freeReading = true;
      else if (right == WRITE)
        freeWriting = true;
      else if (right == -READ)
        freeReading = false;
      else if (right == -WRITE)
        freeWriting = false;
      else
        throw new RequestException("Invalid right value: " + right);
    } else {
      // Setting a specific user right:
      Integer currentRight = (Integer) clients.get(user);
      if (right == READ) {
        if (currentRight != null && currentRight.intValue() == WRITE)
          clients.put(user, new Integer(READWRITE));
        else
          clients.put(user, new Integer(READ));
      } else if (right == WRITE) {
        if (currentRight != null && currentRight.intValue() == READ)
          clients.put(user, new Integer(READWRITE));
        else
          clients.put(user, new Integer(WRITE));
      } else if (right == -READ) {
        if (currentRight != null && currentRight.intValue() == READWRITE)
          clients.put(user, new Integer(WRITE));
        else if (currentRight != null && currentRight.intValue() == READ)
          clients.remove(user);
      } else if (right == -WRITE) {
        if (currentRight != null && currentRight.intValue() == READWRITE)
          clients.put(user, new Integer(READ));
        else if (currentRight != null && currentRight.intValue() == WRITE)
          clients.remove(user);
      } else
        throw new RequestException("Invalid right value: " + right);
    }
  }

  /**
   * Method implementing the reaction to a <code>GetRightsRequest</code>
   * notification requesting the rights about this destination.
   *
   * @exception AccessException  If the requester is not the administrator.
   */
  protected void getRights(AgentId from, GetRightsRequestNot not) throws AccessException {
    if (! isAdministrator(from))
      throw new AccessException("ADMIN right not granted");

    AgentId key;
    int right;
    Vector readers = new Vector();
    Vector writers = new Vector();
    for (Enumeration keys = clients.keys(); keys.hasMoreElements();) {
      key = (AgentId) keys.nextElement();
      right = ((Integer) clients.get(key)).intValue();

      if (right == READ || right == READWRITE) readers.add(key);
      if (right == WRITE || right == READWRITE) writers.add(key);
    }
    forward(from, new GetRightsReplyNot(not, freeReading, freeWriting, readers, writers));
  }


  public static String[] _rights = {":R;", ";W;", ":RW;"};

  /**
   * Returns a string representation of the rights set on this destination.
   *
   * @return the rights set on this destination.
   */
  public String[] getRights() {
    String rigths[] = new String[clients.size()];

    AgentId key;
    int right;

    int i=0;
    for (Enumeration keys = clients.keys(); keys.hasMoreElements();) {
      key = (AgentId) keys.nextElement();
      right = ((Integer) clients.get(key)).intValue();
      rigths[i++] = key.toString() + _rights[right -1];
    }

    return rigths;
  }

  /**
   * Returns a string representation of rights set on this destination for a
   * particular user. The user is pointed out by its unique identifier.
   *
   * @param userid The user's unique identifier.
   * @return the rights set on this destination.
   */
  public String getRight(String userid) {
    AgentId key = AgentId.fromString(userid);
    if (key == null) return userid + ":bad user;";
    Integer right = (Integer) clients.get(key);
    if (right == null) return userid + ":unknown;";

    return userid + _rights[right.intValue() -1];
  }

  /**
   * This method allows to exclude some JMX attribute of getJMXStatistics method.
   * It must be overloaded in subclass.
   *
   * @param attrName name of attribute to test.
   * @return true if the attribute is a valid one.
   */
  protected boolean isValidJMXAttribute(String attrName) {
    if (attrName == null)
      return false;
    return true;
  }
 
  /**
   * Returns values of all valid JMX attributes about the destination.
   *
   * @return a Hashtable containing the values of all valid JMX attributes about the destination.
   *         The keys are the name of corresponding attributes.
   */
  protected final Hashtable getJMXStatistics() {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "Destination.getJMXStatistics()");

    Hashtable stats = null;

    try {
      List attributes = MXWrapper.getAttributeNames(getMBeanName());
      if (attributes != null) {
        stats = new Hashtable(attributes.size());
        for (int k = 0; k < attributes.size(); k++) {
          String name = (String) attributes.get(k);
          if (isValidJMXAttribute(name)) {
            Object value = MXWrapper.getAttribute(getMBeanName(), name);
            if ((value != null) && ((value instanceof String) || (value instanceof Number)))
              stats.put(name, value);
          }
        }
      }
    } catch (Exception exc) {
      logger.log(BasicLevel.ERROR, " getAttributes  on " + getMBeanName() + " error.", exc);
    }

    return stats;
  }

  /**
   * Method implementing the reaction to a <code>ClientMessages</code>
   * notification holding messages sent by a client.
   * <p>
   * If the sender is not a writer on the destination the messages are
   * sent to the DMQ and an exception is thrown. Otherwise, the processing of
   * the received messages is performed in subclasses.
   *
   * @exception AccessException  If the sender is not a WRITER on the
   *              destination.
   */
  protected void clientMessages(AgentId from, ClientMessages not) throws AccessException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "Destination.clientMessages(" + from + ',' + not + ')');

    // If sender is not a writer, sending the messages to the DMQ, and
    // throwing an exception:
    if (!isWriter(from)) {
      DMQManager dmqManager = new DMQManager(not.getDMQId(), dmqId, getId());
      Message msg;
      for (Iterator msgs = not.getMessages().iterator(); msgs.hasNext();) {
        msg = (Message) msgs.next();
        nbMsgsSentToDMQSinceCreation++;
        dmqManager.addDeadMessage(msg, MessageErrorConstants.NOT_WRITEABLE);
        handleDeniedMessage(msg.id, AgentId.fromString(msg.replyToId));
      }
      dmqManager.sendToDMQ();
      throw new AccessException("WRITE right not granted");
    }

    doClientMessages(from, not);

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // for topic performance : must send reply after process ClientMessage
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    if (!not.isPersistent() && !not.getAsyncSend()) {
      forward(from, new SendReplyNot(not.getClientContext(), not.getRequestId()));
    }
  }

  /**
   * Method used to do specific actions when a message is denied because of a
   * lack of rights.
   */
  protected void handleDeniedMessage(String msgId, AgentId replyTo) {
    // Nothing to do, useful in admin topic
  }

  /**
   * Method implementing the reaction to an <code>UnknownAgent</code>
   * notification.
   * <p>
   * If the unknown agent is the DMQ, its identifier is set to null. If it
   * is a client of the destination, it is removed. Specific processing is
   * also done in subclasses.
   */
  protected void unknownAgent(AgentId from, UnknownAgent not) {
    if (isAdministrator(not.agent)) {
      if (logger.isLoggable(BasicLevel.ERROR))
            logger.log(BasicLevel.ERROR,
                       "Admin of dest " + getId() + " does not exist anymore.");
    } else if (not.agent.equals(dmqId)) {
      // state change, so save.
      setSave();
      dmqId = null;
    } else {
      // state change, so save.
      setSave();
      clients.remove(from);
      doUnknownAgent(not);
    }
  }

  /**
   * Method implementing the reaction to a <code>DeleteNot</code>
   * notification requesting the deletion of the destination.
   * <p>
   * The processing is done in subclasses if the sender is an administrator.
   */
  protected void deleteNot(AgentId from, DeleteNot not) {
    if (! isAdministrator(from)) {
      if (logger.isLoggable(BasicLevel.WARN))
        logger.log(BasicLevel.WARN,
                   "Unauthorized deletion request from " + from);
    } else {
      doDeleteNot(not);
      setSave(); // state change, so save.
      deletable = true;
    }
  }
 
  protected void requestGroupNot(AgentId from, RequestGroupNot not) {
    Enumeration en = not.getClientMessages();
    ClientMessages theCM = (ClientMessages) en.nextElement();
    Vector replies = new Vector();
    replies.addElement(new SendReplyNot(
        theCM.getClientContext(),
        theCM.getRequestId()));
    while (en.hasMoreElements()) {
      ClientMessages cm = (ClientMessages) en.nextElement();
      List msgs = cm.getMessages();
      for (int i = 0; i < msgs.size(); i++) {
        theCM.addMessage((Message) msgs.get(i));
      }
      if (! cm.getAsyncSend()) {
        replies.addElement(new SendReplyNot(
            cm.getClientContext(),
            cm.getRequestId()));
      }
    }
   
    doClientMessages(from, theCM);

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // for topic performance : must send reply after process ClientMessage
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    if (!not.isPersistent() && replies.size() > 0) {
      forward(from, new SendRepliesNot(replies));
    }
  }

  /**
   * Checks the reading permission of a given client agent.
   *
   * @param client  AgentId of the client requesting a reading permission.
   */
  protected boolean isReader(AgentId client) {
    if (isAdministrator(client) || freeReading)
      return true;

    Integer clientRight = (Integer) clients.get(client);
    if (clientRight == null) return false;
   
    return ((clientRight.intValue() == READ) ||
            (clientRight.intValue() == READWRITE));
  }

  /**
   * Checks the writing permission of a given client agent.
   *
   * @param client  AgentId of the client requesting a writing permission.
   */
  protected boolean isWriter(AgentId client) {
    if (isAdministrator(client) || freeWriting)
      return true;

    Integer clientRight = (Integer) clients.get(client);
    if (clientRight == null) return false;
   
    return ((clientRight.intValue() == WRITE) ||
            (clientRight.intValue() == READWRITE));
  }

  /**
   * Checks the administering permission of a given client agent.
   *
   * @param client  AgentId of the client requesting an admin permission.
   */
  protected boolean isAdministrator(AgentId client) {
    return AdminTopic.isAdminTopicId(client) || client.equals(adminId);
  }

  abstract protected void doClientMessages(AgentId from, ClientMessages not);
  abstract protected void doUnknownAgent(UnknownAgent not);
  abstract protected void doDeleteNot(DeleteNot not);
 
  public void delete() {
    DeleteDestination request = new DeleteDestination(getDestinationId());
    FwdAdminRequestNot deleteNot = new FwdAdminRequestNot(request, null, null);
    Channel.sendTo(AdminTopic.getDefault(), deleteNot);
  }

  // AF (TODO): We have to define an interface that allow subclass to declare
  // a processing through delegation.

  /**
   * This method is needed to add processing before the standard handling. It
   * is used in subclass of {@link Queue} and {@link Topic}.
   * The incoming messages can be modified or deleted during the processing.
   *
   * @param from The sender of the message
   * @param msgs The incoming messages.
   * @return The incoming messages after processing.
   */
  protected ClientMessages preProcess(AgentId from, ClientMessages msgs) {
    // nothing to do.
    return msgs;
  }

  /**
   * This method is needed to add processing after the standard handling. It
   * is used in subclass of {@link Queue} and {@link Topic}.
   * The incoming messages can be modified or deleted during the processing.
   *
   * @param from The sender of the message
   * @param msgs The incoming messages.
   * @return The incoming messages after processing.
   */
  protected void postProcess(ClientMessages msgs) {
    // nothing to do.
  }
 
  private void writeObject(java.io.ObjectOutputStream out)
    throws IOException {
    out.writeBoolean(deletable);
    out.writeObject(adminId);
    out.writeBoolean(freeReading);
    out.writeBoolean(freeWriting);
    out.writeObject(clients);   
    out.writeObject(dmqId);
    out.writeLong(creationDate);
    out.writeLong(nbMsgsReceiveSinceCreation);
    out.writeLong(nbMsgsDeliverSinceCreation);
    out.writeLong(nbMsgsSentToDMQSinceCreation);
    out.writeLong(period);
    out.writeObject(interceptorsStr);
  }

  private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException {
    deletable = in.readBoolean();
    adminId = (AgentId)in.readObject();
    freeReading = in.readBoolean();
    freeWriting = in.readBoolean();
    clients = (Hashtable)in.readObject();
    dmqId = (AgentId)in.readObject();
    strbuf = new StringBuffer();
    creationDate = in.readLong();
    nbMsgsReceiveSinceCreation = in.readLong();
    nbMsgsDeliverSinceCreation = in.readLong();
    nbMsgsSentToDMQSinceCreation = in.readLong();
    period = in.readLong();
    interceptorsStr = (String) in.readObject();
  }

  // DestinationMBean interface

  /**
   * Returns the unique identifier of the destination.
   *
   * @return the unique identifier of the destination.
   */
  public final String getDestinationId() {
    return getId().toString();
  }

  /**
   * Tests if this destination is free for reading.
   *
   * @return true if anyone can receive messages from this destination;
   *        false otherwise.
   */
  public boolean isFreeReading() {
    return freeReading;
  }

  /**
   * Sets the <code>FreeReading</code> attribute for this destination.
   *
   * @param on  if true anyone can receive message from this destination.
   */
  public void setFreeReading(boolean on) {
    // state change, so save.
    setSave();
    freeReading = on;
  }

  /**
   * Tests if this destination is free for writing.
   *
   * @return true if anyone can send messages to this destination;
   *        false otherwise.
   */
  public boolean isFreeWriting() {
    return freeWriting;
  }

  /**
   * Sets the <code>FreeWriting</code> attribute for this destination.
   *
   * @param on  if true anyone can send message to this destination.
   */
  public void setFreeWriting(boolean on) {
    // state change, so save.
    setSave();
    freeWriting = on;
  }

  /**
   * Return the unique identifier of DMQ set for this destination if any.
   *
   * @return the unique identifier of DMQ set for this destination if any;
   *       null otherwise.
   */
  public String getDMQId() {
    if (dmqId != null)
      return dmqId.toString();
    return null;
  }

  public AgentId getDMQAgentId() {
    return dmqId;
  }

  /**
   * Returns this destination creation time as a long.
   *
   * @return the destination creation time as UTC milliseconds from the epoch.
   */
  public long getCreationTimeInMillis() {
    return creationDate;
  }

  /**
   * Returns this destination creation time through a <code>String</code> of
   * the form: <code>dow mon dd hh:mm:ss zzz yyyy</code>.
   *
   * @return the destination creation time.
   */
  public String getCreationDate() {
    return new Date(creationDate).toString();
  }

  /**
   * Returns the number of messages received since creation time of this
   * destination.
   *
   * @return the number of messages received since creation time.
   */
  abstract public long getNbMsgsReceiveSinceCreation();
 
  /**
   * Returns the number of messages delivered since creation time of this
   * destination. It includes messages all delivered messages to a consumer,
   * already acknowledged or not.
   *
   * @return the number of messages delivered since creation time.
   */
  public long getNbMsgsDeliverSinceCreation() {
    return nbMsgsDeliverSinceCreation;
  }

  /**
   * Returns the number of erroneous messages forwarded to the DMQ since
   * creation time of this destination..
   *
   * @return the number of erroneous messages forwarded to the DMQ.
   */
  public long getNbMsgsSentToDMQSinceCreation() {
    return nbMsgsSentToDMQSinceCreation;
  }

  protected void replyToTopic(AdminReply reply,
                              AgentId replyTo,
                              String requestMsgId,
                              String replyMsgId) {
    Message message = MessageHelper.createMessage(replyMsgId, requestMsgId, getAgentId(), getType());
    try {
      message.setAdminMessage(reply);
      ClientMessages clientMessages = new ClientMessages(-1, -1, message);
      forward(replyTo, clientMessages);
    } catch (Exception exc) {
      if (logger.isLoggable(BasicLevel.ERROR))
        logger.log(BasicLevel.ERROR, "", exc);
      throw new Error(exc.getMessage());
    }
  }
 
  protected final void forward(AgentId to, Notification not) {
    Channel.sendTo(to, not);
  }
 
  /**
   *
   * @param from
   * @param not
   */
  protected void handleAdminRequestNot(AgentId from, FwdAdminRequestNot not) {
    AdminRequest adminRequest = not.getRequest();

    if (adminRequest instanceof SetRight) {
      setRight((SetRight) adminRequest,
               not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetStatsRequest) {
      replyToTopic(new GetStatsReply(getJMXStatistics()),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof SetDMQRequest) {
      // state change, so save.
      setSave();

      if (((SetDMQRequest)adminRequest).getDmqId() != null)
        dmqId = AgentId.fromString(((SetDMQRequest)adminRequest).getDmqId());
      else
        dmqId = null;

      replyToTopic(new AdminReply(true, null),
                   not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof AdminCommandRequest) {
      processAdminCommand((AdminCommandRequest) adminRequest, not.getReplyTo(), not.getRequestMsgId());
    } else {
      logger.log(BasicLevel.ERROR, "Unknown administration request for destination " + getId());
      replyToTopic(new AdminReply(AdminReply.UNKNOWN_REQUEST, null),
                   not.getReplyTo(),
                   not.getRequestMsgId(),
                   not.getReplyMsgId());
     
    }
  }
 
  /**
   * Proccess an admin command.
   *
   * @param request The administration request.
   * @param replyTo The destination to reply.
   * @param requestMsgId The JMS message id needed to reply.
   */
  protected void processAdminCommand(AdminCommandRequest request,  AgentId replyTo, String requestMsgId) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "processAdminCommand(" + request + ", " + replyTo + ", " + requestMsgId + ')');
    Properties prop = null;
    Properties replyProp = null;
    try {
      switch (request.getCommand()) {
      case AdminCommandConstant.CMD_ADD_INTERCEPTORS:
        prop = request.getProp();
        if (interceptors == null)
          interceptors = new ArrayList();
        InterceptorsHelper.addInterceptors((String) prop.get(AdminCommandConstant.INTERCEPTORS), interceptors);
        interceptorsStr = InterceptorsHelper.getListInterceptors(interceptors);
        // state change
        setSave();
        break;
      case AdminCommandConstant.CMD_REMOVE_INTERCEPTORS:
        prop = request.getProp();
        InterceptorsHelper.removeInterceptors((String) prop.get(AdminCommandConstant.INTERCEPTORS), interceptors);
        interceptorsStr = InterceptorsHelper.getListInterceptors(interceptors);
        if (interceptors.isEmpty())
          interceptors = null;
        // state change
        setSave();
        break;
      case AdminCommandConstant.CMD_GET_INTERCEPTORS:
        replyProp = new Properties();
        if (interceptors == null) {
                    replyProp.put(AdminCommandConstant.INTERCEPTORS, "");
        } else {
                  replyProp.put(AdminCommandConstant.INTERCEPTORS, InterceptorsHelper.getListInterceptors(interceptors));
        }
        break;
      case AdminCommandConstant.CMD_REPLACE_INTERCEPTORS:
        prop = request.getProp();
        if (interceptors == null)
          throw new Exception("interceptors == null.");
        InterceptorsHelper.replaceInterceptor(
            ((String) prop.get(AdminCommandConstant.INTERCEPTORS_NEW)),
            ((String) prop.get(AdminCommandConstant.INTERCEPTORS_OLD)),
            interceptors);
            interceptorsStr = InterceptorsHelper.getListInterceptors(interceptors);
        // state change
        setSave();
        break;
      case AdminCommandConstant.CMD_SET_PROPERTIES:
        setProperties(request.getProp(), false);
        setSave();
        break;
      case AdminCommandConstant.CMD_START_HANDLER:
        replyProp = processStartHandler(request.getProp());
        break;
      case AdminCommandConstant.CMD_STOP_HANDLER:
        replyProp = processStopHandler(request.getProp());
        break;

      default:
        throw new Exception("Bad command : \"" + request.getCommand() + "\"");
      }
      // reply
      replyToTopic(new AdminCommandReply(true, AdminCommandConstant.commandNames[request.getCommand()] + " done.", replyProp), replyTo, requestMsgId, requestMsgId);
    } catch (Exception exc) {
      if (logger.isLoggable(BasicLevel.WARN))
        logger.log(BasicLevel.WARN, "", exc);
      replyToTopic(new AdminReply(-1, exc.getMessage()), replyTo, requestMsgId, requestMsgId);
    }
  }
 
  /**
   * Start the acquisition queue/topic handler.
   *
   * @param prop properties for start if needed (can be null)
   * @return properties for the reply.
   * @throws Exception
   */
  protected Properties processStartHandler(Properties prop) throws Exception {
    if (this instanceof AcquisitionQueue) {
      return ((AcquisitionQueue) this).startHandler(prop);
    } else if (this instanceof AcquisitionTopic) {
      return ((AcquisitionTopic) this).startHandler(prop);
    } else {
      throw new Exception("processStartHandler :: bad destination.");
    }
  }
 
  /**
   * Stop the acquisition queue/topic handler.
   *
   * @param prop properties for start if needed (can be null)
   * @return properties for the reply.
   * @throws Exception
   */
  protected Properties processStopHandler(Properties prop) throws Exception {
    if (this instanceof AcquisitionQueue) {
      return ((AcquisitionQueue) this).stopHandler(prop);
    } else if (this instanceof AcquisitionTopic) {
      return ((AcquisitionTopic) this).stopHandler(prop);
    } else {
      throw new Exception("processStopHandler :: bad destination.");
    }
  }
 
  /**
   * Interceptors process
   *
   * @param msg the message
   * @return message potentially modified by the interceptors (message can be null)
   */
  protected Message processInterceptors(Message msg) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "processInterceptors(" + msg + ')');
   
    if (interceptors != null && !interceptors.isEmpty()) {
      Iterator it = interceptors.iterator();
      while (it.hasNext()) {
        MessageInterceptor interceptor = (MessageInterceptor) it.next();
        if (!interceptor.handle(msg))
          return null;
      }
    }
    return msg;
  }
 
  /**
   * @return true if interceptors set
   */
  protected boolean interceptorsAvailable() {
    return interceptors != null && !interceptors.isEmpty();
  }
}
TOP

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

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.