Пример #1
0
  DBPort(InetSocketAddress addr, DBPortPool pool, MongoOptions options) throws IOException {
    _options = options;
    _addr = addr;
    _pool = pool;

    _hashCode = _addr.hashCode();

    _logger = Logger.getLogger(_rootLogger.getName() + "." + addr.toString());
  }
 static {
   m_logger = Logger.getLogger((Class) ExternalFightCreationMessageHandler.class);
 }
Пример #3
0
/**
 * Class is a transport layer for WebRTC data channels. It consists of SCTP connection running on
 * top of ICE/DTLS layer. Manages WebRTC data channels. See
 * http://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-08 for more info on WebRTC data
 * channels.
 *
 * <p>Control protocol: http://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-03 FIXME handle
 * closing of data channels(SCTP stream reset)
 *
 * @author Pawel Domas
 * @author Lyubomir Marinov
 * @author Boris Grozev
 */
public class SctpConnection extends Channel
    implements SctpDataCallback, SctpSocket.NotificationListener {
  /** Generator used to track debug IDs. */
  private static int debugIdGen = -1;

  /** DTLS transport buffer size. Note: randomly chosen. */
  private static final int DTLS_BUFFER_SIZE = 2048;

  /** Switch used for debugging SCTP traffic purposes. FIXME to be removed */
  private static final boolean LOG_SCTP_PACKETS = false;

  /** The logger */
  private static final Logger logger = Logger.getLogger(SctpConnection.class);

  /**
   * Message type used to acknowledge WebRTC data channel allocation on SCTP stream ID on which
   * <tt>MSG_OPEN_CHANNEL</tt> message arrives.
   */
  private static final int MSG_CHANNEL_ACK = 0x2;

  private static final byte[] MSG_CHANNEL_ACK_BYTES = new byte[] {MSG_CHANNEL_ACK};

  /**
   * Message with this type sent over control PPID in order to open new WebRTC data channel on SCTP
   * stream ID that this message is sent.
   */
  private static final int MSG_OPEN_CHANNEL = 0x3;

  /** SCTP transport buffer size. */
  private static final int SCTP_BUFFER_SIZE = DTLS_BUFFER_SIZE - 13;

  /** The pool of <tt>Thread</tt>s which run <tt>SctpConnection</tt>s. */
  private static final ExecutorService threadPool =
      ExecutorUtils.newCachedThreadPool(true, SctpConnection.class.getName());

  /** Payload protocol id that identifies binary data in WebRTC data channel. */
  static final int WEB_RTC_PPID_BIN = 53;

  /** Payload protocol id for control data. Used for <tt>WebRtcDataStream</tt> allocation. */
  static final int WEB_RTC_PPID_CTRL = 50;

  /** Payload protocol id that identifies text data UTF8 encoded in WebRTC data channels. */
  static final int WEB_RTC_PPID_STRING = 51;

  /**
   * The <tt>String</tt> value of the <tt>Protocol</tt> field of the <tt>DATA_CHANNEL_OPEN</tt>
   * message.
   */
  private static final String WEBRTC_DATA_CHANNEL_PROTOCOL = "http://jitsi.org/protocols/colibri";

  private static synchronized int generateDebugId() {
    debugIdGen += 2;
    return debugIdGen;
  }

  /**
   * Indicates whether the STCP association is ready and has not been ended by a subsequent state
   * change.
   */
  private boolean assocIsUp;

  /** Indicates if we have accepted incoming connection. */
  private boolean acceptedIncomingConnection;

  /** Data channels mapped by SCTP stream identified(sid). */
  private final Map<Integer, WebRtcDataStream> channels = new HashMap<Integer, WebRtcDataStream>();

  /** Debug ID used to distinguish SCTP sockets in packet logs. */
  private final int debugId;

  /**
   * The <tt>AsyncExecutor</tt> which is to asynchronously dispatch the events fired by this
   * instance in order to prevent possible listeners from blocking this <tt>SctpConnection</tt> in
   * general and {@link #sctpSocket} in particular for too long. The timeout of <tt>15</tt> is
   * chosen to be in accord with the time it takes to expire a <tt>Channel</tt>.
   */
  private final AsyncExecutor<Runnable> eventDispatcher =
      new AsyncExecutor<Runnable>(15, TimeUnit.MILLISECONDS);

  /** Datagram socket for ICE/UDP layer. */
  private IceSocketWrapper iceSocket;

  /**
   * List of <tt>WebRtcDataStreamListener</tt>s that will be notified whenever new WebRTC data
   * channel is opened.
   */
  private final List<WebRtcDataStreamListener> listeners =
      new ArrayList<WebRtcDataStreamListener>();

  /** Remote SCTP port. */
  private final int remoteSctpPort;

  /** <tt>SctpSocket</tt> used for SCTP transport. */
  private SctpSocket sctpSocket;

  /**
   * Flag prevents from starting this connection multiple times from {@link #maybeStartStream()}.
   */
  private boolean started;

  /**
   * Initializes a new <tt>SctpConnection</tt> instance.
   *
   * @param id the string identifier of this connection instance
   * @param content the <tt>Content</tt> which is initializing the new instance
   * @param endpoint the <tt>Endpoint</tt> of newly created instance
   * @param remoteSctpPort the SCTP port used by remote peer
   * @param channelBundleId the ID of the channel-bundle this <tt>SctpConnection</tt> is to be a
   *     part of (or <tt>null</tt> if no it is not to be a part of a channel-bundle).
   * @throws Exception if an error occurs while initializing the new instance
   */
  public SctpConnection(
      String id, Content content, Endpoint endpoint, int remoteSctpPort, String channelBundleId)
      throws Exception {
    super(content, id, channelBundleId);

    setEndpoint(endpoint.getID());

    this.remoteSctpPort = remoteSctpPort;
    this.debugId = generateDebugId();
  }

  /**
   * Adds <tt>WebRtcDataStreamListener</tt> to the list of listeners.
   *
   * @param listener the <tt>WebRtcDataStreamListener</tt> to be added to the listeners list.
   */
  public void addChannelListener(WebRtcDataStreamListener listener) {
    if (listener == null) {
      throw new NullPointerException("listener");
    } else {
      synchronized (listeners) {
        if (!listeners.contains(listener)) {
          listeners.add(listener);
        }
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  protected void closeStream() throws IOException {
    try {
      synchronized (this) {
        assocIsUp = false;
        acceptedIncomingConnection = false;
        if (sctpSocket != null) {
          sctpSocket.close();
          sctpSocket = null;
        }
      }
    } finally {
      if (iceSocket != null) {
        // It is now the responsibility of the transport manager to
        // close the socket.
        // iceUdpSocket.close();
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public void expire() {
    try {
      eventDispatcher.shutdown();
    } finally {
      super.expire();
    }
  }

  /**
   * Gets the <tt>WebRtcDataStreamListener</tt>s added to this instance.
   *
   * @return the <tt>WebRtcDataStreamListener</tt>s added to this instance or <tt>null</tt> if there
   *     are no <tt>WebRtcDataStreamListener</tt>s added to this instance
   */
  private WebRtcDataStreamListener[] getChannelListeners() {
    WebRtcDataStreamListener[] ls;

    synchronized (listeners) {
      if (listeners.isEmpty()) {
        ls = null;
      } else {
        ls = listeners.toArray(new WebRtcDataStreamListener[listeners.size()]);
      }
    }
    return ls;
  }

  /**
   * Returns default <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise.
   *
   * @return <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise.
   * @throws IOException
   */
  public WebRtcDataStream getDefaultDataStream() throws IOException {
    WebRtcDataStream def;

    synchronized (this) {
      if (sctpSocket == null) {
        def = null;
      } else {
        // Channel that runs on sid 0
        def = channels.get(0);
        if (def == null) {
          def = openChannel(0, 0, 0, 0, "default");
        }
        // Pawel Domas: Must be acknowledged before use
        /*
         * XXX Lyubomir Marinov: We're always sending ordered. According
         * to "WebRTC Data Channel Establishment Protocol", we can start
         * sending messages containing user data after the
         * DATA_CHANNEL_OPEN message has been sent without waiting for
         * the reception of the corresponding DATA_CHANNEL_ACK message.
         */
        //                if (!def.isAcknowledged())
        //                    def = null;
      }
    }
    return def;
  }

  /**
   * Returns <tt>true</tt> if this <tt>SctpConnection</tt> is connected to the remote peer and
   * operational.
   *
   * @return <tt>true</tt> if this <tt>SctpConnection</tt> is connected to the remote peer and
   *     operational
   */
  public boolean isReady() {
    return assocIsUp && acceptedIncomingConnection;
  }

  /** {@inheritDoc} */
  @Override
  protected void maybeStartStream() throws IOException {
    // connector
    final StreamConnector connector = getStreamConnector();

    if (connector == null) return;

    synchronized (this) {
      if (started) return;

      threadPool.execute(
          new Runnable() {
            @Override
            public void run() {
              try {
                Sctp.init();

                runOnDtlsTransport(connector);
              } catch (IOException e) {
                logger.error(e, e);
              } finally {
                try {
                  Sctp.finish();
                } catch (IOException e) {
                  logger.error("Failed to shutdown SCTP stack", e);
                }
              }
            }
          });

      started = true;
    }
  }

  /**
   * Submits {@link #notifyChannelOpenedInEventDispatcher(WebRtcDataStream)} to {@link
   * #eventDispatcher} for asynchronous execution.
   *
   * @param dataChannel
   */
  private void notifyChannelOpened(final WebRtcDataStream dataChannel) {
    if (!isExpired()) {
      eventDispatcher.execute(
          new Runnable() {
            @Override
            public void run() {
              notifyChannelOpenedInEventDispatcher(dataChannel);
            }
          });
    }
  }

  private void notifyChannelOpenedInEventDispatcher(WebRtcDataStream dataChannel) {
    /*
     * When executing asynchronously in eventDispatcher, it is technically
     * possible that this SctpConnection may have expired by now.
     */
    if (!isExpired()) {
      WebRtcDataStreamListener[] ls = getChannelListeners();

      if (ls != null) {
        for (WebRtcDataStreamListener l : ls) {
          l.onChannelOpened(this, dataChannel);
        }
      }
    }
  }

  /**
   * Submits {@link #notifySctpConnectionReadyInEventDispatcher()} to {@link #eventDispatcher} for
   * asynchronous execution.
   */
  private void notifySctpConnectionReady() {
    if (!isExpired()) {
      eventDispatcher.execute(
          new Runnable() {
            @Override
            public void run() {
              notifySctpConnectionReadyInEventDispatcher();
            }
          });
    }
  }

  /**
   * Notifies the <tt>WebRtcDataStreamListener</tt>s added to this instance that this
   * <tt>SctpConnection</tt> is ready i.e. it is connected to the remote peer and operational.
   */
  private void notifySctpConnectionReadyInEventDispatcher() {
    /*
     * When executing asynchronously in eventDispatcher, it is technically
     * possible that this SctpConnection may have expired by now.
     */
    if (!isExpired() && isReady()) {
      WebRtcDataStreamListener[] ls = getChannelListeners();

      if (ls != null) {
        for (WebRtcDataStreamListener l : ls) {
          l.onSctpConnectionReady(this);
        }
      }
    }
  }

  /**
   * Handles control packet.
   *
   * @param data raw packet data that arrived on control PPID.
   * @param sid SCTP stream id on which the data has arrived.
   */
  private synchronized void onCtrlPacket(byte[] data, int sid) throws IOException {
    ByteBuffer buffer = ByteBuffer.wrap(data);
    int messageType = /* 1 byte unsigned integer */ 0xFF & buffer.get();

    if (messageType == MSG_CHANNEL_ACK) {
      if (logger.isDebugEnabled()) {
        logger.debug(getEndpoint().getID() + " ACK received SID: " + sid);
      }
      // Open channel ACK
      WebRtcDataStream channel = channels.get(sid);
      if (channel != null) {
        // Ack check prevents from firing multiple notifications
        // if we get more than one ACKs (by mistake/bug).
        if (!channel.isAcknowledged()) {
          channel.ackReceived();
          notifyChannelOpened(channel);
        } else {
          logger.warn("Redundant ACK received for SID: " + sid);
        }
      } else {
        logger.error("No channel exists on sid: " + sid);
      }
    } else if (messageType == MSG_OPEN_CHANNEL) {
      int channelType = /* 1 byte unsigned integer */ 0xFF & buffer.get();
      int priority = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      long reliability = /* 4 bytes unsigned integer */ 0xFFFFFFFFL & buffer.getInt();
      int labelLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      int protocolLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      String label;
      String protocol;

      if (labelLength == 0) {
        label = "";
      } else {
        byte[] labelBytes = new byte[labelLength];

        buffer.get(labelBytes);
        label = new String(labelBytes, "UTF-8");
      }
      if (protocolLength == 0) {
        protocol = "";
      } else {
        byte[] protocolBytes = new byte[protocolLength];

        buffer.get(protocolBytes);
        protocol = new String(protocolBytes, "UTF-8");
      }

      if (logger.isDebugEnabled()) {
        logger.debug(
            "!!! "
                + getEndpoint().getID()
                + " data channel open request on SID: "
                + sid
                + " type: "
                + channelType
                + " prio: "
                + priority
                + " reliab: "
                + reliability
                + " label: "
                + label
                + " proto: "
                + protocol);
      }

      if (channels.containsKey(sid)) {
        logger.error("Channel on sid: " + sid + " already exists");
      }

      WebRtcDataStream newChannel = new WebRtcDataStream(sctpSocket, sid, label, true);
      channels.put(sid, newChannel);

      sendOpenChannelAck(sid);

      notifyChannelOpened(newChannel);
    } else {
      logger.error("Unexpected ctrl msg type: " + messageType);
    }
  }

  /** {@inheritDoc} */
  @Override
  protected void onEndpointChanged(Endpoint oldValue, Endpoint newValue) {
    if (oldValue != null) oldValue.setSctpConnection(null);
    if (newValue != null) newValue.setSctpConnection(this);
  }

  /** Implements notification in order to track socket state. */
  @Override
  public synchronized void onSctpNotification(SctpSocket socket, SctpNotification notification) {
    if (logger.isDebugEnabled()) {
      logger.debug("socket=" + socket + "; notification=" + notification);
    }
    switch (notification.sn_type) {
      case SctpNotification.SCTP_ASSOC_CHANGE:
        SctpNotification.AssociationChange assocChange =
            (SctpNotification.AssociationChange) notification;

        switch (assocChange.state) {
          case SctpNotification.AssociationChange.SCTP_COMM_UP:
            if (!assocIsUp) {
              boolean wasReady = isReady();

              assocIsUp = true;
              if (isReady() && !wasReady) notifySctpConnectionReady();
            }
            break;

          case SctpNotification.AssociationChange.SCTP_COMM_LOST:
          case SctpNotification.AssociationChange.SCTP_SHUTDOWN_COMP:
          case SctpNotification.AssociationChange.SCTP_CANT_STR_ASSOC:
            try {
              closeStream();
            } catch (IOException e) {
              logger.error("Error closing SCTP socket", e);
            }
            break;
        }
        break;
    }
  }

  /**
   * {@inheritDoc}
   *
   * <p>SCTP input data callback.
   */
  @Override
  public void onSctpPacket(
      byte[] data, int sid, int ssn, int tsn, long ppid, int context, int flags) {
    if (ppid == WEB_RTC_PPID_CTRL) {
      // Channel control PPID
      try {
        onCtrlPacket(data, sid);
      } catch (IOException e) {
        logger.error("IOException when processing ctrl packet", e);
      }
    } else if (ppid == WEB_RTC_PPID_STRING || ppid == WEB_RTC_PPID_BIN) {
      WebRtcDataStream channel;

      synchronized (this) {
        channel = channels.get(sid);
      }

      if (channel == null) {
        logger.error("No channel found for sid: " + sid);
        return;
      }
      if (ppid == WEB_RTC_PPID_STRING) {
        // WebRTC String
        String str;
        String charsetName = "UTF-8";

        try {
          str = new String(data, charsetName);
        } catch (UnsupportedEncodingException uee) {
          logger.error("Unsupported charset encoding/name " + charsetName, uee);
          str = null;
        }
        channel.onStringMsg(str);
      } else {
        // WebRTC Binary
        channel.onBinaryMsg(data);
      }
    } else {
      logger.warn("Got message on unsupported PPID: " + ppid);
    }
  }

  /**
   * Opens new WebRTC data channel using specified parameters.
   *
   * @param type channel type as defined in control protocol description. Use 0 for "reliable".
   * @param prio channel priority. The higher the number, the lower the priority.
   * @param reliab Reliability Parameter<br>
   *     This field is ignored if a reliable channel is used. If a partial reliable channel with
   *     limited number of retransmissions is used, this field specifies the number of
   *     retransmissions. If a partial reliable channel with limited lifetime is used, this field
   *     specifies the maximum lifetime in milliseconds. The following table summarizes this:<br>
   *     </br>
   *     <p>+------------------------------------------------+------------------+ | Channel Type |
   *     Reliability | | | Parameter |
   *     +------------------------------------------------+------------------+ |
   *     DATA_CHANNEL_RELIABLE | Ignored | | DATA_CHANNEL_RELIABLE_UNORDERED | Ignored | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT | Number of RTX | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED | Number of RTX | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_TIMED | Lifetime in ms | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED | Lifetime in ms |
   *     +------------------------------------------------+------------------+
   * @param sid SCTP stream id that will be used by new channel (it must not be already used).
   * @param label text label for the channel.
   * @return new instance of <tt>WebRtcDataStream</tt> that represents opened WebRTC data channel.
   * @throws IOException if IO error occurs.
   */
  public synchronized WebRtcDataStream openChannel(
      int type, int prio, long reliab, int sid, String label) throws IOException {
    if (channels.containsKey(sid)) {
      throw new IOException("Channel on sid: " + sid + " already exists");
    }

    // Label Length & Label
    byte[] labelBytes;
    int labelByteLength;

    if (label == null) {
      labelBytes = null;
      labelByteLength = 0;
    } else {
      labelBytes = label.getBytes("UTF-8");
      labelByteLength = labelBytes.length;
      if (labelByteLength > 0xFFFF) labelByteLength = 0xFFFF;
    }

    // Protocol Length & Protocol
    String protocol = WEBRTC_DATA_CHANNEL_PROTOCOL;
    byte[] protocolBytes;
    int protocolByteLength;

    if (protocol == null) {
      protocolBytes = null;
      protocolByteLength = 0;
    } else {
      protocolBytes = protocol.getBytes("UTF-8");
      protocolByteLength = protocolBytes.length;
      if (protocolByteLength > 0xFFFF) protocolByteLength = 0xFFFF;
    }

    ByteBuffer packet = ByteBuffer.allocate(12 + labelByteLength + protocolByteLength);

    // Message open new channel on current sid
    // Message Type
    packet.put((byte) MSG_OPEN_CHANNEL);
    // Channel Type
    packet.put((byte) type);
    // Priority
    packet.putShort((short) prio);
    // Reliability Parameter
    packet.putInt((int) reliab);
    // Label Length
    packet.putShort((short) labelByteLength);
    // Protocol Length
    packet.putShort((short) protocolByteLength);
    // Label
    if (labelByteLength != 0) {
      packet.put(labelBytes, 0, labelByteLength);
    }
    // Protocol
    if (protocolByteLength != 0) {
      packet.put(protocolBytes, 0, protocolByteLength);
    }

    int sentCount = sctpSocket.send(packet.array(), true, sid, WEB_RTC_PPID_CTRL);

    if (sentCount != packet.capacity()) {
      throw new IOException("Failed to open new chanel on sid: " + sid);
    }

    WebRtcDataStream channel = new WebRtcDataStream(sctpSocket, sid, label, false);

    channels.put(sid, channel);

    return channel;
  }

  /**
   * Removes <tt>WebRtcDataStreamListener</tt> from the list of listeners.
   *
   * @param listener the <tt>WebRtcDataStreamListener</tt> to be removed from the listeners list.
   */
  public void removeChannelListener(WebRtcDataStreamListener listener) {
    if (listener != null) {
      synchronized (listeners) {
        listeners.remove(listener);
      }
    }
  }

  private void runOnDtlsTransport(StreamConnector connector) throws IOException {
    DtlsControlImpl dtlsControl = (DtlsControlImpl) getTransportManager().getDtlsControl(this);
    DtlsTransformEngine engine = dtlsControl.getTransformEngine();
    final DtlsPacketTransformer transformer = (DtlsPacketTransformer) engine.getRTPTransformer();

    byte[] receiveBuffer = new byte[SCTP_BUFFER_SIZE];

    if (LOG_SCTP_PACKETS) {
      System.setProperty(
          ConfigurationService.PNAME_SC_HOME_DIR_LOCATION, System.getProperty("java.io.tmpdir"));
      System.setProperty(
          ConfigurationService.PNAME_SC_HOME_DIR_NAME, SctpConnection.class.getName());
    }

    synchronized (this) {
      // FIXME local SCTP port is hardcoded in bridge offer SDP (Jitsi
      // Meet)
      sctpSocket = Sctp.createSocket(5000);
      assocIsUp = false;
      acceptedIncomingConnection = false;
    }

    // Implement output network link for SCTP stack on DTLS transport
    sctpSocket.setLink(
        new NetworkLink() {
          @Override
          public void onConnOut(SctpSocket s, byte[] packet) throws IOException {
            if (LOG_SCTP_PACKETS) {
              LibJitsi.getPacketLoggingService()
                  .logPacket(
                      PacketLoggingService.ProtocolName.ICE4J,
                      new byte[] {0, 0, 0, (byte) debugId},
                      5000,
                      new byte[] {0, 0, 0, (byte) (debugId + 1)},
                      remoteSctpPort,
                      PacketLoggingService.TransportName.UDP,
                      true,
                      packet);
            }

            // Send through DTLS transport
            transformer.sendApplicationData(packet, 0, packet.length);
          }
        });

    if (logger.isDebugEnabled()) {
      logger.debug("Connecting SCTP to port: " + remoteSctpPort + " to " + getEndpoint().getID());
    }

    sctpSocket.setNotificationListener(this);
    sctpSocket.listen();

    // FIXME manage threads
    threadPool.execute(
        new Runnable() {
          @Override
          public void run() {
            SctpSocket sctpSocket = null;
            try {
              // sctpSocket is set to null on close
              sctpSocket = SctpConnection.this.sctpSocket;
              while (sctpSocket != null) {
                if (sctpSocket.accept()) {
                  acceptedIncomingConnection = true;
                  break;
                }
                Thread.sleep(100);
                sctpSocket = SctpConnection.this.sctpSocket;
              }
              if (isReady()) {
                notifySctpConnectionReady();
              }
            } catch (Exception e) {
              logger.error("Error accepting SCTP connection", e);
            }

            if (sctpSocket == null && logger.isInfoEnabled()) {
              logger.info(
                  "SctpConnection " + getID() + " closed" + " before SctpSocket accept()-ed.");
            }
          }
        });

    // Notify that from now on SCTP connection is considered functional
    sctpSocket.setDataCallback(this);

    // Setup iceSocket
    DatagramSocket datagramSocket = connector.getDataSocket();
    if (datagramSocket != null) {
      this.iceSocket = new IceUdpSocketWrapper(datagramSocket);
    } else {
      this.iceSocket = new IceTcpSocketWrapper(connector.getDataTCPSocket());
    }

    DatagramPacket rcvPacket = new DatagramPacket(receiveBuffer, 0, receiveBuffer.length);

    // Receive loop, breaks when SCTP socket is closed
    try {
      do {
        iceSocket.receive(rcvPacket);

        RawPacket raw =
            new RawPacket(rcvPacket.getData(), rcvPacket.getOffset(), rcvPacket.getLength());

        raw = transformer.reverseTransform(raw);
        // Check for app data
        if (raw == null) continue;

        if (LOG_SCTP_PACKETS) {
          LibJitsi.getPacketLoggingService()
              .logPacket(
                  PacketLoggingService.ProtocolName.ICE4J,
                  new byte[] {0, 0, 0, (byte) (debugId + 1)},
                  remoteSctpPort,
                  new byte[] {0, 0, 0, (byte) debugId},
                  5000,
                  PacketLoggingService.TransportName.UDP,
                  false,
                  raw.getBuffer(),
                  raw.getOffset(),
                  raw.getLength());
        }

        // Pass network packet to SCTP stack
        sctpSocket.onConnIn(raw.getBuffer(), raw.getOffset(), raw.getLength());
      } while (true);
    } finally {
      // Eventually, close the socket although it should happen from
      // expire().
      synchronized (this) {
        assocIsUp = false;
        acceptedIncomingConnection = false;
        if (sctpSocket != null) {
          sctpSocket.close();
          sctpSocket = null;
        }
      }
    }
  }

  /**
   * Sends acknowledgment for open channel request on given SCTP stream ID.
   *
   * @param sid SCTP stream identifier to be used for sending ack.
   */
  private void sendOpenChannelAck(int sid) throws IOException {
    // Send ACK
    byte[] ack = MSG_CHANNEL_ACK_BYTES;
    int sendAck = sctpSocket.send(ack, true, sid, WEB_RTC_PPID_CTRL);

    if (sendAck != ack.length) {
      logger.error("Failed to send open channel confirmation");
    }
  }

  /**
   * {@inheritDoc}
   *
   * <p>Creates a <tt>TransportManager</tt> instance suitable for an <tt>SctpConnection</tt> (e.g.
   * with 1 component only).
   */
  protected TransportManager createTransportManager(String xmlNamespace) throws IOException {
    if (IceUdpTransportPacketExtension.NAMESPACE.equals(xmlNamespace)) {
      Content content = getContent();
      return new IceUdpTransportManager(
          content.getConference(), isInitiator(), 1 /* num components */, content.getName());
    } else if (RawUdpTransportPacketExtension.NAMESPACE.equals(xmlNamespace)) {
      // TODO: support RawUdp once RawUdpTransportManager is updated
      // return new RawUdpTransportManager(this);
      throw new IllegalArgumentException("Unsupported Jingle transport " + xmlNamespace);
    } else {
      throw new IllegalArgumentException("Unsupported Jingle transport " + xmlNamespace);
    }
  }
}
Пример #4
0
/**
 * The class represents the recurring pattern structure of calendar item.
 *
 * @author Hristo Terezov
 */
public class RecurringPattern {
  /**
   * The <tt>Logger</tt> used by the <tt>RecurringPattern</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(RecurringPattern.class);

  /** Enum for the type of the pattern. */
  public enum PatternType {
    /** Daily recurrence. */
    Day((short) 0x0000),

    /** Weekly recurrence. */
    Week((short) 0x0001),

    /** Monthly recurrence. */
    Month((short) 0x0002),

    /** Monthly recurrence. */
    MonthNth((short) 0x0003),

    /** Monthly recurrence. */
    MonthEnd((short) 0x004),

    /** Monthly recurrence. */
    HjMonth((short) 0x000A),

    /** Monthly recurrence. */
    HjMonthNth((short) 0x000B),

    /** Monthly recurrence. */
    HjMonthEnd((short) 0x000C);

    /** The value of the type. */
    private short value = 0;

    /**
     * Constructs new <tt>PatternType</tt> instance.
     *
     * @param value the value.
     */
    PatternType(short value) {
      this.value = value;
    }

    /**
     * Returns the value of the <tt>PatternType</tt> instance.
     *
     * @return the value
     */
    public short getValue() {
      return value;
    }

    /**
     * Finds the <tt>PatternType</tt> by given value.
     *
     * @param value the value
     * @return the found <tt>PatternType</tt> instance or null if no type is found.
     */
    public static PatternType getFromShort(short value) {
      for (PatternType type : values()) {
        if (type.getValue() == value) {
          return type;
        }
      }
      return null;
    }
  };

  /** The value of recurFrequency field. */
  private short recurFrequency;

  /** The value of patternType field. */
  private PatternType patternType;

  /** The value of calendarType field. */
  private short calendarType;

  /** The value of firstDateTime field. */
  private int firstDateTime;

  /** The value of period field. */
  private int period;

  /** The value of slidingFlag field. */
  private int slidingFlag;

  /** The value of patternSpecific1 field. */
  private int patternSpecific1;

  /** The value of patternSpecific2 field. */
  private int patternSpecific2;

  /** The value of endType field. */
  private int endType;

  /** The value of occurenceCount field. */
  private int occurenceCount;

  /** The value of firstDow field. */
  private int firstDow;

  /** The value of deletedInstanceCount field. */
  private int deletedInstanceCount;

  /** The value of modifiedInstanceCount field. */
  private int modifiedInstanceCount;

  /** The value of startDate field. */
  private int startDate;

  /** The value of endDate field. */
  private int endDate;

  /** List with the start dates of deleted instances. */
  private List<Date> deletedInstances = new ArrayList<Date>();

  /** Array with the start dates of modified instances. */
  private int[] modifiedInstances;

  /** List of exception info structures included in the pattern. */
  private List<ExceptionInfo> exceptionInfo;

  /** The source calendar item of the recurrent series. */
  private CalendarItemTimerTask sourceTask;

  /** List of days of week when the calendar item occurred. */
  private List<Integer> allowedDaysOfWeek = new LinkedList<Integer>();

  /** The binary data of the pattern. */
  private ByteBuffer dataBuffer;

  /** Array with masks for days of week when the calendar item occurs. */
  public static int[] weekOfDayMask = {
    0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040
  };

  /**
   * Parses the binary data that describes the recurrent pattern.
   *
   * @param data the binary data.
   * @param sourceTask the calendar item.
   */
  public RecurringPattern(byte[] data, CalendarItemTimerTask sourceTask) {
    this.sourceTask = sourceTask;
    dataBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);

    int offset = 4;
    recurFrequency = dataBuffer.getShort(offset);
    offset += 2;

    patternType = PatternType.getFromShort(dataBuffer.getShort(offset));
    offset += 2;

    calendarType = dataBuffer.getShort(offset);
    offset += 2;

    firstDateTime = dataBuffer.getInt(offset);
    offset += 4;

    period = dataBuffer.getInt(offset);
    offset += 4;

    slidingFlag = dataBuffer.getInt(offset);
    offset += 4;

    switch (patternType) {
      case Week:
      case Month:
      case MonthEnd:
      case HjMonth:
      case HjMonthEnd:
        patternSpecific1 = dataBuffer.getInt(offset);
        patternSpecific2 = 0;
        offset += 4;
        if (patternType == PatternType.Week) {
          for (int day = firstDow; day < firstDow + 7; day++) {
            if ((patternSpecific1 & (weekOfDayMask[day % 7])) != 0) {
              allowedDaysOfWeek.add((day % 7) + 1);
            }
          }
        }
        break;
      case MonthNth:
      case HjMonthNth:
        patternSpecific1 = dataBuffer.getInt(offset);
        patternSpecific2 = dataBuffer.getInt(offset + 4);
        if (patternSpecific1 == 0x7f && patternSpecific2 != 0x5) {
          patternType = PatternType.Month;
        }
        for (int day = 0; day < 7; day++) {
          if ((patternSpecific1 & (weekOfDayMask[day])) != 0) {
            allowedDaysOfWeek.add((day) + 1);
          }
        }
        offset += 8;
        break;
      default:
        break;
    }

    // endType
    endType = dataBuffer.getInt(offset);
    offset += 4;

    occurenceCount = dataBuffer.getInt(offset);
    offset += 4;

    firstDow = dataBuffer.getInt(offset);
    offset += 4;

    deletedInstanceCount = dataBuffer.getInt(offset);
    offset += 4;

    // deleted instances
    for (int i = 0; i < deletedInstanceCount; i++) {
      deletedInstances.add(windowsTimeToDateObject(dataBuffer.getInt(offset)));
      offset += 4;
    }

    modifiedInstanceCount = dataBuffer.getInt(offset);
    offset += 4;

    // modified instances
    modifiedInstances = new int[modifiedInstanceCount];

    for (int i = 0; i < modifiedInstanceCount; i++) {
      modifiedInstances[i] = dataBuffer.getInt(offset);
      offset += 4;
    }

    startDate = dataBuffer.getInt(offset);
    offset += 4;

    endDate = dataBuffer.getInt(offset);
    offset += 4;

    offset += 16;

    short exceptionCount = dataBuffer.getShort(offset);
    offset += 2;
    exceptionInfo = new ArrayList<ExceptionInfo>(exceptionCount);
    for (int i = 0; i < exceptionCount; i++) {
      ExceptionInfo tmpExceptionInfo = new ExceptionInfo(offset);
      exceptionInfo.add(tmpExceptionInfo);
      offset += tmpExceptionInfo.sizeInBytes();

      CalendarService.BusyStatusEnum status = tmpExceptionInfo.getBusyStatus();
      Date startTime = tmpExceptionInfo.getStartDate();
      Date endTime = tmpExceptionInfo.getEndDate();
      if (status == CalendarService.BusyStatusEnum.FREE || startTime == null || endTime == null)
        continue;
      Date currentTime = new Date();

      if (endTime.before(currentTime) || endTime.equals(currentTime)) return;

      boolean executeNow = false;

      if (startTime.before(currentTime) || startTime.equals(currentTime)) executeNow = true;

      CalendarItemTimerTask task =
          new CalendarItemTimerTask(
              status, startTime, endTime, sourceTask.getId(), executeNow, this);

      task.scheduleTasks();
    }
  }

  /**
   * Converts windows time in minutes from 1/1/1601 to <tt>Date</tt> object.
   *
   * @param time the number of minutes from 1/1/1601
   * @return the <tt>Date</tt> object
   */
  public static Date windowsTimeToDateObject(long time) {
    // Date.parse("1/1/1601") == 11644473600000L
    long date = time * 60000 - 11644473600000L;
    date -= TimeZone.getDefault().getOffset(date);
    return new Date(date);
  }

  /** Prints the properties of the class for debugging purpose. */
  @Override
  public String toString() {
    String result = "";
    result += "recurFrequency: " + String.format("%#02x", this.recurFrequency) + "\n";
    result += "patternType: " + String.format("%#02x", this.patternType.getValue()) + "\n";
    result += "calendarType: " + String.format("%#02x", this.calendarType) + "\n";
    result += "endType: " + String.format("%#04x", this.endType) + "\n";

    result += "period: " + this.period + "\n";
    result += "occurenceCount: " + String.format("%#04x", this.occurenceCount) + "\n";
    result += "patternSpecific1: " + String.format("%#04x", this.patternSpecific1) + "\n";
    result += "patternSpecific2: " + String.format("%#04x", this.patternSpecific2) + "\n";
    result += "startDate hex: " + String.format("%#04x", this.startDate) + "\n";
    result += "endDate hex: " + String.format("%#04x", this.endDate) + "\n";

    result +=
        "startDate: "
            + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(windowsTimeToDateObject(this.startDate))
            + "\n";
    result +=
        "endDate: "
            + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(windowsTimeToDateObject(this.endDate))
            + "\n";

    for (int i = 0; i < modifiedInstanceCount; i++) {
      result +=
          "modified Instance date hex: " + String.format("%#04x", this.modifiedInstances[i]) + "\n";

      result +=
          "modified Instance date: "
              + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z")
                  .format(windowsTimeToDateObject(this.modifiedInstances[i]))
              + "\n";
    }

    for (int i = 0; i < deletedInstanceCount; i++) {
      result +=
          "deleted Instance date: "
              + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(deletedInstances.get(i))
              + "\n";
    }
    result += "patternSpecific2: " + String.format("%#04x", this.patternSpecific2) + "\n";

    result += "\n\n =====================Exeptions====================\n\n";

    for (ExceptionInfo info : exceptionInfo) {
      result += info.toString() + "\n\n";
    }
    return result;
  }

  /**
   * Checks whether the given date is in the recurrent pattern range or not
   *
   * @param date the date
   * @return <tt>true</tt> if the date is in the pattern range.
   */
  private boolean dateOutOfRange(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    if ((endType != 0x00002023)
        && (endType != 0xFFFFFFFF)
        && cal.getTime().after(windowsTimeToDateObject(this.endDate))) {
      return true; // the series are finished
    }
    return false;
  }

  /**
   * Calculates and creates the next calendar item.
   *
   * @param previousStartDate the start date of the previous occurrence.
   * @param previousEndDate the end date of the previous occurrence.
   * @return the new calendar item or null if there are no more calendar items from that recurrent
   *     series.
   */
  public CalendarItemTimerTask next(Date previousStartDate, Date previousEndDate) {
    if (dateOutOfRange(new Date())) {
      return null;
    }
    Date startDate = previousStartDate;
    Date endDate = null;
    boolean executeNow = false;
    long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime();
    switch (patternType) {
      case Day:
        {
          startDate = new Date(startDate.getTime() + period * 60000);
          endDate = new Date(previousEndDate.getTime() + period * 60000);
          Date currentDate = new Date();
          if (endDate.before(currentDate)) {
            long offset = currentDate.getTime() - endDate.getTime();
            offset -= offset % (period * 60000);
            if (endDate.getTime() + offset < currentDate.getTime()) {
              offset += period * 60000;
            }

            startDate = new Date(startDate.getTime() + offset);
          }

          Calendar cal = Calendar.getInstance();
          cal.setTime(startDate);
          Calendar cal2 = (Calendar) cal.clone();
          cal.set(Calendar.HOUR_OF_DAY, 0);
          cal.set(Calendar.MINUTE, 0);
          cal.set(Calendar.SECOND, 0);
          cal.set(Calendar.MILLISECOND, 0);
          while (deletedInstances.contains(cal.getTime())) {
            cal.add(Calendar.MINUTE, period);
            cal2.add(Calendar.MINUTE, period);
          }

          if (dateOutOfRange(cal.getTime())) {
            return null;
          }
          startDate = cal2.getTime();
          endDate = new Date(startDate.getTime() + duration);
          if (startDate.before(currentDate)) {
            executeNow = true;
          }

          return new CalendarItemTimerTask(
              sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
        }
      case Week:
        {
          Calendar cal = Calendar.getInstance();
          /** The enum for the firstDow field is the same as Calendar day of week enum + 1 day */
          cal.setFirstDayOfWeek(firstDow + 1);
          cal.setTime(startDate);
          int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
          int index = allowedDaysOfWeek.indexOf(dayOfWeek);
          if (++index < allowedDaysOfWeek.size()) {
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index));
            startDate = cal.getTime();
            endDate = new Date(startDate.getTime() + duration);
          } else {
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(0));
            cal.add(Calendar.WEEK_OF_YEAR, period);
            startDate = cal.getTime();
            endDate = new Date(startDate.getTime() + duration);
          }
          Date currentDate = new Date();
          if (endDate.before(currentDate)) {
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(0));
            endDate = new Date(cal.getTimeInMillis() + duration);
            long offset = (currentDate.getTime() - endDate.getTime());

            // 1 week = 604800000 is milliseconds
            offset -= offset % (period * 604800000);
            if (endDate.getTime() + offset < currentDate.getTime()) {
              cal.add(Calendar.WEEK_OF_YEAR, (int) (offset / (period * 604800000)));
              int i = 1;
              while (((cal.getTimeInMillis() + duration) < (currentDate.getTime()))) {
                if (i == allowedDaysOfWeek.size()) {
                  cal.add(Calendar.WEEK_OF_YEAR, period);
                  i = 0;
                }
                cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(i));
                i++;
              }

              startDate = cal.getTime();
            } else {
              startDate = new Date(cal.getTimeInMillis() + offset);
            }
          }

          cal.setTime(startDate);
          Calendar cal2 = (Calendar) cal.clone();
          cal.set(Calendar.HOUR_OF_DAY, 0);
          cal.set(Calendar.MINUTE, 0);
          cal.set(Calendar.SECOND, 0);
          cal.set(Calendar.MILLISECOND, 0);
          dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
          index = allowedDaysOfWeek.indexOf(dayOfWeek) + 1;
          while (deletedInstances.contains(cal.getTime())) {
            if (index >= allowedDaysOfWeek.size()) {
              index = 0;
              cal.add(Calendar.WEEK_OF_YEAR, period);
              cal2.add(Calendar.WEEK_OF_YEAR, period);
            }
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index));
            cal2.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index));
            index++;
          }
          startDate = cal2.getTime();
          endDate = new Date(startDate.getTime() + duration);
          if (dateOutOfRange(endDate)) return null;
          if (startDate.before(currentDate)) {
            executeNow = true;
          }

          return new CalendarItemTimerTask(
              sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
        }
      case Month:
      case MonthEnd:
      case HjMonth:
      case HjMonthEnd:
        {
          return nextMonth(startDate, endDate, false);
        }
      case MonthNth:
      case HjMonthNth:
        {
          if (patternSpecific1 == 0x7f && patternSpecific2 == 0x05) {
            return nextMonth(startDate, endDate, true);
          }

          return nextMonthN(startDate, endDate);
        }
    }
    return null;
  }

  /**
   * Finds the occurrence of the events in the next months
   *
   * @param cal the calendar object
   * @param lastDay if <tt>true</tt> it will return the last day of the month
   * @param period the number of months to add
   * @return the calendar object with set date
   */
  private Calendar incrementMonths(Calendar cal, boolean lastDay, int period) {
    int dayOfMonth = patternSpecific1;
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.add(Calendar.MONTH, period);
    if (lastDay || (cal.getActualMaximum(Calendar.DAY_OF_MONTH) < dayOfMonth))
      dayOfMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);

    cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    return cal;
  }

  /**
   * Finds the next occurrence for monthly recurrence.
   *
   * @param startDate the start date of the previous calendar item.
   * @param endDate the end date of the previous calendar item.
   * @param lastDay if <tt>true</tt> we are interested in last day of the month
   * @return the next item
   */
  public CalendarItemTimerTask nextMonth(Date startDate, Date endDate, boolean lastDay) {
    long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime();
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);
    cal = incrementMonths(cal, lastDay, period);
    Date currentDate = new Date();
    if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
      Calendar cal2 = Calendar.getInstance();
      cal2.setTime(currentDate);
      int years = cal2.get(Calendar.YEAR) - cal.get(Calendar.YEAR);
      int months = (years * 12) + (cal2.get(Calendar.MONTH) - cal.get(Calendar.MONTH));
      int monthsToAdd = months;
      monthsToAdd -= months % period;
      cal = incrementMonths(cal, lastDay, monthsToAdd);
      if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
        cal = incrementMonths(cal, lastDay, period);
      }
    }

    Calendar cal2 = (Calendar) cal.clone();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    while (deletedInstances.contains(cal.getTime())) {
      cal = incrementMonths(cal, lastDay, period);
      cal2 = incrementMonths(cal2, lastDay, period);
    }

    startDate = cal2.getTime();
    endDate = new Date(startDate.getTime() + duration);
    if (dateOutOfRange(endDate)) {
      return null;
    }
    boolean executeNow = false;
    if (startDate.before(currentDate)) {
      executeNow = true;
    }

    return new CalendarItemTimerTask(
        sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
  }

  /**
   * Finds the occurrence of the events in the next months
   *
   * @param startDate the start date if the calendar item
   * @param dayOfWeekInMonth the number of week days occurrences
   * @return the date of the next occurrence
   */
  private Date getMonthNStartDate(Date startDate, int dayOfWeekInMonth) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);

    if (dayOfWeekInMonth == -1) {
      Date result = null;
      cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth);
      for (int day : allowedDaysOfWeek) {
        cal.set(Calendar.DAY_OF_WEEK, day);
        if (result == null || result.before(cal.getTime())) result = cal.getTime();
      }
      return result;
    } else
      while (dayOfWeekInMonth > 0) {
        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        if (allowedDaysOfWeek.contains(dayOfWeek)) dayOfWeekInMonth--;
        if (dayOfWeekInMonth > 0) cal.add(Calendar.DAY_OF_MONTH, 1);
      }
    return cal.getTime();
  }

  /**
   * Finds the next occurrence for monthly Nth recurrence.
   *
   * @param startDate the start date of the previous calendar item.
   * @param endDate the end date of the previous calendar item.
   * @return the next item
   */
  public CalendarItemTimerTask nextMonthN(Date startDate, Date endDate) {
    int dayOfWeekInMonth = (patternSpecific2 == 5 ? -1 : patternSpecific2);
    long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime();
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.add(Calendar.MONTH, period);
    cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth));
    Date currentDate = new Date();
    if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
      Calendar cal2 = Calendar.getInstance();
      cal2.setTime(currentDate);
      int years = cal2.get(Calendar.YEAR) - cal.get(Calendar.YEAR);
      int months = (years * 12) + (cal2.get(Calendar.MONTH) - cal.get(Calendar.MONTH));
      int monthsToAdd = months;
      monthsToAdd -= months % period;
      cal.set(Calendar.DAY_OF_MONTH, 1);
      cal.add(Calendar.MONTH, monthsToAdd);
      cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth));
      if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.add(Calendar.MONTH, monthsToAdd);
        cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth));
      }
    }

    Calendar cal2 = (Calendar) cal.clone();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    while (deletedInstances.contains(cal.getTime())) {
      cal.set(Calendar.DAY_OF_MONTH, 1);
      cal.add(Calendar.MONTH, period);
      startDate = null;
      for (int dayOfWeek : allowedDaysOfWeek) {
        cal.set(Calendar.DAY_OF_WEEK, dayOfWeek);
        cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth);
        if ((cal.after(startDate) && dayOfWeekInMonth == -1)
            || (cal.before(startDate) && dayOfWeekInMonth != -1)
            || startDate == null) {
          startDate = cal.getTime();
          cal2.set(Calendar.YEAR, cal.get(Calendar.YEAR));
          cal2.set(Calendar.MONTH, cal.get(Calendar.MONTH));
          cal2.set(Calendar.DATE, cal.get(Calendar.DATE));
        }
      }
    }

    startDate = cal2.getTime();
    endDate = new Date(startDate.getTime() + duration);

    if (dateOutOfRange(endDate)) return null;

    boolean executeNow = false;
    if (startDate.before(currentDate)) {
      executeNow = true;
    }

    return new CalendarItemTimerTask(
        sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
  }

  /** Represents the exception info structure. */
  public class ExceptionInfo {
    /** The start date of the exception. */
    private final Date startDate;

    /** The end date of the exception. */
    private final Date endDate;

    /** The original start date of the exception. */
    private final Date originalStartDate;

    /** The modified flags of the exception. */
    private final short overrideFlags;

    /** The new busy status of the exception. */
    private CalendarService.BusyStatusEnum busyStatus;

    /** The size of the fixed fields. */
    private int size = 22;

    /**
     * Parses the data of the exception.
     *
     * @param offset the position where the exception starts in the binary data
     */
    public ExceptionInfo(int offset) {
      startDate = windowsTimeToDateObject(dataBuffer.getInt(offset));
      offset += 4;

      endDate = windowsTimeToDateObject(dataBuffer.getInt(offset));
      offset += 4;

      originalStartDate = windowsTimeToDateObject(dataBuffer.getInt(offset));
      offset += 4;

      overrideFlags = dataBuffer.getShort(offset);
      offset += 2;
      int[] fieldMasks = {0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080};
      for (int mask : fieldMasks) {
        if (mask == 0x0020) {
          if ((overrideFlags & mask) != 0) {
            busyStatus =
                CalendarService.BusyStatusEnum.getFromLong((long) dataBuffer.getInt(offset));
          }

          if (busyStatus == null) {
            busyStatus = sourceTask.getStatus();
          }
        }

        if ((overrideFlags & mask) != 0) {
          if (mask == 0x0010 || mask == 0x0001) {
            short size = dataBuffer.getShort(offset + 2);
            offset += size;
            size += size;
          }
          offset += 4;
          size += 4;
        }
      }

      offset += 4;
      int reservedBlockSize = dataBuffer.getShort(offset);
      size += reservedBlockSize;
    }

    /**
     * Returns the size of the exception
     *
     * @return the size of the exception
     */
    public int sizeInBytes() {
      return size;
    }

    /**
     * Returns the start date
     *
     * @return the start date
     */
    public Date getStartDate() {
      return startDate;
    }

    /**
     * Returns the end date
     *
     * @return the end date
     */
    public Date getEndDate() {
      return endDate;
    }

    /**
     * Returns the busy status
     *
     * @return the busy status
     */
    public CalendarService.BusyStatusEnum getBusyStatus() {
      return busyStatus;
    }

    /** Prints the properties of the class for debugging purpose. */
    @Override
    public String toString() {
      String result = "";
      result +=
          "startDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(startDate) + "\n";
      result += "endDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(endDate) + "\n";
      result +=
          "originalStartDate: "
              + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(originalStartDate)
              + "\n";
      return result;
    }
  }
}
Пример #5
0
public class DBPort {

  public static final int PORT = 27017;
  static final boolean USE_NAGLE = false;

  static final long CONN_RETRY_TIME_MS = 15000;

  public DBPort(InetSocketAddress addr) throws IOException {
    this(addr, null, new MongoOptions());
  }

  DBPort(InetSocketAddress addr, DBPortPool pool, MongoOptions options) throws IOException {
    _options = options;
    _addr = addr;
    _pool = pool;

    _hashCode = _addr.hashCode();

    _logger = Logger.getLogger(_rootLogger.getName() + "." + addr.toString());
  }

  /** @param response will get wiped */
  DBMessage call(DBMessage msg, ByteDecoder decoder) throws IOException {
    return go(msg, decoder);
  }

  void say(DBMessage msg) throws IOException {
    go(msg, null);
  }

  private synchronized DBMessage go(DBMessage msg, ByteDecoder decoder) throws IOException {

    if (_sock == null) _open();

    {
      ByteBuffer out = msg.prepare();
      while (out.remaining() > 0) _sock.write(out);
    }

    if (_pool != null) _pool._everWorked = true;

    if (decoder == null) return null;

    ByteBuffer response = decoder._buf;

    if (response.position() != 0) throw new IllegalArgumentException();

    int read = 0;
    while (read < DBMessage.HEADER_LENGTH) read += _read(response);

    int len = response.getInt(0);
    if (len <= DBMessage.HEADER_LENGTH)
      throw new IllegalArgumentException("db sent invalid length: " + len);

    if (len > response.capacity())
      throw new IllegalArgumentException(
          "db message size is too big (" + len + ") " + "max is (" + response.capacity() + ")");

    response.limit(len);
    while (read < len) read += _read(response);

    if (read != len) throw new RuntimeException("something is wrong");

    response.flip();
    return new DBMessage(response);
  }

  public synchronized void ensureOpen() throws IOException {

    if (_sock != null) return;

    _open();
  }

  void _open() throws IOException {

    long sleepTime = 100;

    final long start = System.currentTimeMillis();
    while (true) {

      IOException lastError = null;

      try {
        _sock = SocketChannel.open();
        _socket = _sock.socket();
        _socket.connect(_addr, _options.connectTimeout);

        _socket.setTcpNoDelay(!USE_NAGLE);
        _socket.setSoTimeout(_options.socketTimeout);
        _in = _socket.getInputStream();
        return;
      } catch (IOException ioe) {
        //  TODO  - erh to fix                lastError = new IOException( "couldn't connect to [" +
        // _addr + "] bc:" + lastError , lastError );
        lastError = new IOException("couldn't connect to [" + _addr + "] bc:" + ioe);
        _logger.log(Level.INFO, "connect fail to : " + _addr, ioe);
      }

      if (!_options.autoConnectRetry || (_pool != null && !_pool._everWorked)) throw lastError;

      long sleptSoFar = System.currentTimeMillis() - start;

      if (sleptSoFar >= CONN_RETRY_TIME_MS) throw lastError;

      if (sleepTime + sleptSoFar > CONN_RETRY_TIME_MS) sleepTime = CONN_RETRY_TIME_MS - sleptSoFar;

      _logger.severe(
          "going to sleep and retry.  total sleep time after = "
              + (sleptSoFar + sleptSoFar)
              + "ms  this time:"
              + sleepTime
              + "ms");
      ThreadUtil.sleep(sleepTime);
      sleepTime *= 2;
    }
  }

  public int hashCode() {
    return _hashCode;
  }

  public String host() {
    return _addr.toString();
  }

  public String toString() {
    return "{DBPort  " + host() + "}";
  }

  protected void finalize() {
    if (_sock != null) {
      try {
        _sock.close();
      } catch (Exception e) {
        // don't care
      }

      _in = null;
      _socket = null;
      _sock = null;
    }
  }

  void checkAuth(DB db) {
    if (db._username == null) return;
    if (_authed.containsKey(db)) return;

    if (_inauth) return;

    _inauth = true;
    try {
      if (db.reauth()) {
        _authed.put(db, true);
        return;
      }
    } finally {
      _inauth = false;
    }

    throw new MongoInternalException("can't reauth!");
  }

  private int _read(ByteBuffer buf) throws IOException {
    int x = _in.read(buf.array(), buf.position(), buf.remaining());
    if (x < 0) throw new IOException("connection to server closed unexpectedly");
    buf.position(buf.position() + x);
    return x;
  }

  final int _hashCode;
  final InetSocketAddress _addr;
  final DBPortPool _pool;
  final MongoOptions _options;
  final Logger _logger;

  private SocketChannel _sock;
  private Socket _socket;
  private InputStream _in;

  private boolean _inauth = false;
  private Map<DB, Boolean> _authed = Collections.synchronizedMap(new WeakHashMap<DB, Boolean>());

  private static Logger _rootLogger = Logger.getLogger("com.mongodb.port");
}
 static {
   AbstractDungeonLadder.m_logger = Logger.getLogger((Class) AbstractDungeonLadder.class);
 }
 static {
   m_logger = Logger.getLogger((Class) JOrbisStream.class);
   m_bigEndian = (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN);
 }
Пример #8
0
 static {
   m_logger = Logger.getLogger((Class) Dummy.class);
 }
 static {
   m_logger = Logger.getLogger((Class) SearchTreasureOccupationStartMessage.class);
 }
Пример #10
0
/**
 * A reactor that selects on some stuff and then notifies some Communicators that things happened
 */
public class Overlord {
  private Selector selector;
  private Pipe pipe;
  private static final Logger log = Logger.getLogger("Overlord");
  private ConcurrentLinkedQueue<Communicator> queue;

  // This is just used to read the one byte off of pipes informing us that
  // there is data on some queue.
  ByteBuffer ignored = ByteBuffer.allocate(10);

  public Overlord() {
    try {
      selector = Selector.open();
      queue = new ConcurrentLinkedQueue<Communicator>();

      // open the pipe and register it with our selector
      pipe = Pipe.open();
      pipe.sink().configureBlocking(false);
      pipe.source().configureBlocking(false);
      pipe.source().register(selector, SelectionKey.OP_READ);
    } catch (IOException e) {
      throw new RuntimeException("select() failed");
    }
  }

  /** Selects on sockets and informs their Communicator when there is something to do. */
  public void communicate(int timeout) {

    try {
      selector.select(timeout);
    } catch (IOException e) {
      // Not really sure why/when this happens yet
      return;
    }

    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

    while (keys.hasNext()) {
      SelectionKey key = keys.next();
      keys.remove();
      if (!key.isValid()) continue; // WHY
      Communicator communicator = (Communicator) key.attachment();

      if (key.isReadable()) communicator.onReadable();
      if (key.isWritable()) communicator.onWritable();
      if (key.isAcceptable()) communicator.onAcceptable();
    }

    // Go through the queue and handle each communicator
    while (!queue.isEmpty()) {
      Communicator c = queue.poll();
      c.onMemo();
    }
  }

  public void offer(Communicator c) {
    queue.offer(c);
  }

  /** Registers a SelectableChannel */
  public boolean register(SelectableChannel sc, Communicator communicator) {
    try {
      sc.register(selector, sc.validOps(), communicator);

      return true;
    } catch (Exception e) {
      return false;
    }
  }

  /** If the selector is waiting, wake it up */
  public void interrupt() {
    selector.wakeup();
  }

  /** Registers a SelectableQueue */
  public boolean register(SelectableQueue sq, Communicator communicator) {
    try {
      // Register the new pipe with the queue. It will write a byte to this
      // pipe when the queue is hot, and it will offer its communicator to our
      // queue.
      sq.register(this, communicator);

      return true;
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }
}
Пример #11
0
public class CodingTest extends TestCase {

  private static final Logger LOG = Logger.getLogger(CodingTest.class.getName());

  /**
   * Constructor for this unit test.
   *
   * @param testName the name of the unit test
   */
  public CodingTest(String testName) {
    super(testName);
  }

  /**
   * Allows unit tests to be run together in a suite.
   *
   * @return a test suite that contains a single test - this one
   */
  public static Test suite() {
    return new TestSuite(CodingTest.class);
  }

  /**
   * Allows test to be run stand-alone from the command-line.
   *
   * <p>java -Djava.library.path=/opt/honeycomb/lib -classpath
   * test/lib/junit-3.8.1.jar:test/classes:classes com.sun.honeycomb.coding.CodingTest
   */
  public static void main(String args[]) {
    junit.textui.TestRunner.run(suite());
  }

  public void setUp() {}

  public void tearDown() {}

  public void testByteBufferCoder() {
    Persistent3 three =
        new Persistent3(
            10,
            (float) 4.444,
            555555555555L,
            true,
            "this is the fourth persistent string",
            new Date(),
            new byte[] {10, 20, 30, 40, 50, 60, 70, 80, 90, 100},
            null);

    Persistent2 two =
        new Persistent2(
            1,
            (float) 2.222,
            333333333333L,
            false,
            "this is the first persistent string",
            new Date(),
            new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
            three,
            4,
            "this is the second persistent string",
            (ByteBuffer) ByteBuffer.allocateDirect(17).putInt(1234).rewind());

    Persistent1 one =
        new Persistent1(
            2,
            (float) 3.333,
            4444444444444L,
            true,
            "this is the third persistent string",
            new Date(),
            new byte[] {10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
            two);

    ByteBuffer buffer = ByteBuffer.allocateDirect(8 * 1024);
    ByteBufferCoder encoder = new ByteBufferCoder(buffer, true);

    encoder.encodeCodable(one);
    buffer.rewind();

    ByteBufferCoder decoder = new ByteBufferCoder(buffer, true, new Delegate());
    Persistent1 anotherOne = (Persistent1) decoder.decodeCodable();

    if (!one.equals(anotherOne)) {
      fail("unarchived object with type checking does not equal archived one");
    }

    buffer.clear();
    encoder = new ByteBufferCoder(buffer, false);

    encoder.encodeCodable(one);
    buffer.rewind();

    decoder = new ByteBufferCoder(buffer, false, new Delegate());
    anotherOne = (Persistent1) decoder.decodeCodable();

    if (!one.equals(anotherOne)) {
      fail("unarchived object without type checking does not equal archived one");
    }
  }

  public static class Persistent1 implements Codable, Serializable {
    private int int1;
    private float float1;
    private long long1;
    private boolean boolean1;
    private String string1;
    private Date date1;
    private byte[] bytes1;
    private Codable codable1;

    public Persistent1() {}

    public Persistent1(
        int newInt,
        float newFloat,
        long newLong,
        boolean newBoolean,
        String newString,
        Date newDate,
        byte[] newBytes,
        Codable newCodable) {
      int1 = newInt;
      float1 = newFloat;
      long1 = newLong;
      boolean1 = newBoolean;
      string1 = newString;
      date1 = newDate;
      bytes1 = newBytes;
      codable1 = newCodable;
    }

    public void encode(Encoder encoder) {
      encoder.encodeInt(int1);
      encoder.encodeFloat(float1);
      encoder.encodeLong(long1);
      encoder.encodeBoolean(boolean1);
      encoder.encodeString(string1);
      encoder.encodeDate(date1);
      encoder.encodeBytes(bytes1);
      encoder.encodeCodable(codable1);
    }

    public void decode(Decoder decoder) {
      int1 = decoder.decodeInt();
      float1 = decoder.decodeFloat();
      long1 = decoder.decodeLong();
      boolean1 = decoder.decodeBoolean();
      string1 = decoder.decodeString();
      date1 = decoder.decodeDate();
      bytes1 = decoder.decodeBytes();
      codable1 = decoder.decodeCodable();
    }

    public boolean equals(Object other) {
      Persistent1 otherPersistent1 = (Persistent1) other;

      if (int1 != otherPersistent1.int1) {
        System.out.println("int1 failed");
        return false;
      }

      if (float1 != otherPersistent1.float1) {
        System.out.println("float1 failed");
        return false;
      }

      if (long1 != otherPersistent1.long1) {
        System.out.println("long1 failed");
        return false;
      }

      if (boolean1 != otherPersistent1.boolean1) {
        System.out.println("boolean1 failed");
        return false;
      }

      if (string1 != otherPersistent1.string1
          && (string1 != null
              && !string1.equals(otherPersistent1.string1)
              && otherPersistent1.string1 != null)) {
        System.out.println("string1 failed");
        return false;
      }

      if (date1 != otherPersistent1.date1
          && (date1 != null
              && !date1.equals(otherPersistent1.date1)
              && otherPersistent1.date1 != null)) {
        System.out.println("date1 failed");
        return false;
      }

      if (bytes1 != otherPersistent1.bytes1
          && (bytes1 != null
              && !Arrays.equals(bytes1, otherPersistent1.bytes1)
              && otherPersistent1.bytes1 != null)) {
        System.out.println("bytes1 failed");
        return false;
      }

      if (codable1 != otherPersistent1.codable1
          && (codable1 != null
              && !codable1.equals(otherPersistent1.codable1)
              && otherPersistent1.codable1 != null)) {
        System.out.println("codable1 failed");
        return false;
      }

      return true;
    }
  }

  private static class Persistent2 extends Persistent1 {
    private int int2;
    private String string2;
    private ByteBuffer buffer2;

    public Persistent2() {}

    public Persistent2(
        int newInt,
        float newFloat,
        long newLong,
        boolean newBoolean,
        String newString,
        Date newDate,
        byte[] newBytes,
        Codable newCodable,
        int newInt2,
        String newString2,
        ByteBuffer newBuffer2) {
      super(newInt, newFloat, newLong, newBoolean, newString, newDate, newBytes, newCodable);

      int2 = newInt2;
      string2 = newString2;
      buffer2 = newBuffer2;
    }

    public void encode(Encoder encoder) {
      super.encode(encoder);

      encoder.encodeInt(int2);
      encoder.encodeString(string2);
      encoder.encodeInt(buffer2.remaining());
      encoder.encodeKnownLengthBuffer(buffer2);
    }

    public void decode(Decoder decoder) {
      super.decode(decoder);

      int2 = decoder.decodeInt();
      string2 = decoder.decodeString();

      int length = decoder.decodeInt();
      buffer2 = decoder.decodeKnownLengthBuffer(length, true);
    }

    public boolean equals(Object other) {
      if (!super.equals(other)) {
        return false;
      }

      Persistent2 otherPersistent2 = (Persistent2) other;

      if (int2 != otherPersistent2.int2) {
        System.out.println("int2 failed");
        return false;
      }

      if (string2 != otherPersistent2.string2
          && (string2 != null
              && !string2.equals(otherPersistent2.string2)
              && otherPersistent2.string2 != null)) {
        System.out.println("string2 failed");
        return false;
      }

      if (buffer2 != otherPersistent2.buffer2
          && (buffer2 != null
              && !buffer2.equals(otherPersistent2.buffer2)
              && otherPersistent2.buffer2 != null)) {
        System.out.println("buffer2 failed");
        return false;
      }

      return true;
    }
  }

  static class Persistent3 extends Persistent1 {

    Persistent3() {}

    Persistent3(
        int newInt,
        float newFloat,
        long newLong,
        boolean newBoolean,
        String newString,
        Date newDate,
        byte[] newBytes,
        Codable newCodable) {
      super(newInt, newFloat, newLong, newBoolean, newString, newDate, newBytes, newCodable);
    }
  }

  static class Delegate implements Decoder.Delegate {
    public Codable newInstance(String className)
        throws ClassNotFoundException, IllegalAccessException, InstantiationException {
      return (Codable) Class.forName(className).newInstance();
    }
  }
}
Пример #12
0
class DBTCPConnector implements DBConnector {

  static Logger _logger = Logger.getLogger(Bytes.LOGGER.getName() + ".tcp");
  static Logger _createLogger = Logger.getLogger(_logger.getName() + ".connect");

  public DBTCPConnector(Mongo m, ServerAddress addr) throws MongoException {
    _mongo = m;
    _portHolder = new DBPortPool.Holder(m._options);
    _checkAddress(addr);

    _createLogger.info(addr.toString());

    if (addr.isPaired()) {
      _allHosts = new ArrayList<ServerAddress>(addr.explode());
      _createLogger.info("switch to paired mode : " + _allHosts + " -> " + _curAddress);
    } else {
      _set(addr);
      _allHosts = null;
    }
  }

  public DBTCPConnector(Mongo m, ServerAddress... all) throws MongoException {
    this(m, Arrays.asList(all));
  }

  public DBTCPConnector(Mongo m, List<ServerAddress> all) throws MongoException {
    _mongo = m;
    _portHolder = new DBPortPool.Holder(m._options);
    _checkAddress(all);

    _allHosts = new ArrayList<ServerAddress>(all); // make a copy so it can't be modified

    _createLogger.info(all + " -> " + _curAddress);
  }

  private static ServerAddress _checkAddress(ServerAddress addr) {
    if (addr == null) throw new NullPointerException("address can't be null");
    return addr;
  }

  private static ServerAddress _checkAddress(List<ServerAddress> addrs) {
    if (addrs == null) throw new NullPointerException("addresses can't be null");
    if (addrs.size() == 0) throw new IllegalArgumentException("need to specify at least 1 address");
    return addrs.get(0);
  }

  /**
   * Start a "request".
   *
   * <p>A "request" is a group of operations in which order matters. Examples include inserting a
   * document and then performing a query which expects that document to have been inserted, or
   * performing an operation and then using com.mongodb.Mongo.getLastError to perform error-checking
   * on that operation. When a thread performs operations in a "request", all operations will be
   * performed on the same socket, so they will be correctly ordered.
   */
  public void requestStart() {
    _threadPort.get().requestStart();
  }

  /**
   * End the current "request", if this thread is in one.
   *
   * <p>By ending a request when it is safe to do so the built-in connection- pool is allowed to
   * reassign requests to different sockets in order to more effectively balance load. See
   * requestStart for more information.
   */
  public void requestDone() {
    _threadPort.get().requestDone();
  }

  public void requestEnsureConnection() {
    _threadPort.get().requestEnsureConnection();
  }

  WriteResult _checkWriteError(MyPort mp, DBPort port) throws MongoException {

    CommandResult e = _mongo.getDB("admin").getLastError();
    mp.done(port);

    Object foo = e.get("err");
    if (foo == null) return new WriteResult(e);

    int code = -1;
    if (e.get("code") instanceof Number) code = ((Number) e.get("code")).intValue();
    String s = foo.toString();
    if (code == 11000 || code == 11001 || s.startsWith("E11000") || s.startsWith("E11001"))
      throw new MongoException.DuplicateKey(code, s);
    throw new MongoException(code, s);
  }

  public WriteResult say(DB db, OutMessage m, DB.WriteConcern concern) throws MongoException {
    MyPort mp = _threadPort.get();
    DBPort port = mp.get(true);
    port.checkAuth(db);

    try {
      port.say(m);
      if (concern == DB.WriteConcern.STRICT) {
        return _checkWriteError(mp, port);
      } else {
        mp.done(port);
        return new WriteResult(db, port);
      }
    } catch (IOException ioe) {
      mp.error(ioe);
      _error(ioe);
      if (concern == DB.WriteConcern.NONE) {
        CommandResult res = new CommandResult();
        res.put("ok", false);
        res.put("$err", "NETWORK ERROR");
        return new WriteResult(res);
      }
      throw new MongoException.Network("can't say something", ioe);
    } catch (MongoException me) {
      throw me;
    } catch (RuntimeException re) {
      mp.error(re);
      throw re;
    }
  }

  public Response call(DB db, DBCollection coll, OutMessage m) throws MongoException {
    return call(db, coll, m, 2);
  }

  public Response call(DB db, DBCollection coll, OutMessage m, int retries) throws MongoException {

    final MyPort mp = _threadPort.get();
    final DBPort port = mp.get(false);

    port.checkAuth(db);

    Response res = null;
    try {
      res = port.call(m, coll);
      mp.done(port);
    } catch (IOException ioe) {
      mp.error(ioe);
      if (_error(ioe) && retries > 0) {
        return call(db, coll, m, retries - 1);
      }
      throw new MongoException.Network("can't call something", ioe);
    } catch (RuntimeException re) {
      mp.error(re);
      throw re;
    }

    ServerError err = res.getError();

    if (err != null && err.isNotMasterError()) {
      _pickCurrent();
      if (retries <= 0) {
        throw new MongoException("not talking to master and retries used up");
      }
      return call(db, coll, m, retries - 1);
    }

    return res;
  }

  public ServerAddress getAddress() {
    return _curAddress;
  }

  public List<ServerAddress> getAllAddress() {
    return _allHosts;
  }

  public String getConnectPoint() {
    return _curAddress.toString();
  }

  boolean _error(Throwable t) throws MongoException {
    if (_allHosts != null) {
      System.out.println("paired mode, switching master b/c of: " + t);
      t.printStackTrace();
      _pickCurrent();
    }
    return true;
  }

  class MyPort {

    DBPort get(boolean keep) {
      _internalStack++;

      if (_internalStack > 1) {
        if (_last == null) {
          System.err.println("_internalStack > 1 and _last is null!");
        } else {
          return _last;
        }
      }

      if (_port != null) return _port;

      try {
        DBPort p = _curPortPool.get();
        if (keep && _inRequest) _port = p;

        _last = p;
        return p;
      } catch (DBPortPool.NoMoreConnection nmc) {
        _internalStack = 0;
        throw nmc;
      }
    }

    void done(DBPort p) {

      if (_internalStack <= 0) {
        int prev = _internalStack;
        _reset();
        throw new IllegalStateException("done called and _internalStack was: " + _internalStack);
      }

      _internalStack--;

      if (p != _port && _internalStack == 0) _curPortPool.done(p);

      if (_internalStack < 0) {
        System.err.println("_internalStack < 0 : " + _internalStack);
        _internalStack = 0;
      }
    }

    void error(Exception e) {
      _curPortPool.remove(_port);
      _curPortPool.gotError(e);

      _internalStack = 0;
      _last = null;
    }

    void requestEnsureConnection() {
      if (!_inRequest) return;

      if (_port != null) return;

      _port = _curPortPool.get();
    }

    void requestStart() {
      _inRequest = true;
      if (_port != null) {
        _port = null;
        System.err.println("ERROR.  somehow _port was not null at requestStart");
      }
    }

    void requestDone() {
      if (_port != null) _curPortPool.done(_port);
      _port = null;
      _inRequest = false;
      if (_internalStack > 0) {
        System.err.println("_internalStack in requestDone should be 0 is: " + _internalStack);
        _internalStack = 0;
      }
    }

    void _reset() {
      _internalStack = 0;
      _port = null;
      _last = null;
    }

    int _internalStack = 0;

    DBPort _port;
    DBPort _last;
    boolean _inRequest;
  }

  void _pickInitial() throws MongoException {
    if (_curAddress != null) return;

    // we need to just get a server to query for ismaster
    _pickCurrent();

    try {
      _logger.info("current address beginning of _pickInitial: " + _curAddress);

      DBObject im = isMasterCmd();
      if (_isMaster(im)) return;

      synchronized (_allHosts) {
        Collections.shuffle(_allHosts);
        for (ServerAddress a : _allHosts) {
          if (_curAddress == a) continue;

          _logger.info("remote [" + _curAddress + "] -> [" + a + "]");
          _set(a);

          im = isMasterCmd();
          if (_isMaster(im)) return;

          _logger.severe("switched to: " + a + " but isn't master");
        }

        throw new MongoException("can't find master");
      }
    } catch (Exception e) {
      _logger.log(Level.SEVERE, "can't pick initial master, using random one", e);
    }
  }

  private void _pickCurrent() throws MongoException {
    if (_allHosts == null)
      throw new MongoException(
          "got master/slave issue but not in master/slave mode on the client side");

    synchronized (_allHosts) {
      Collections.shuffle(_allHosts);
      for (int i = 0; i < _allHosts.size(); i++) {
        ServerAddress a = _allHosts.get(i);
        if (a == _curAddress) continue;

        if (_curAddress != null) {
          _logger.info("switching from [" + _curAddress + "] to [" + a + "]");
        }

        _set(a);
        return;
      }
    }

    throw new MongoException("couldn't find a new host to swtich too");
  }

  private boolean _set(ServerAddress addr) {
    if (_curAddress == addr) return false;
    _curAddress = addr;
    _curPortPool = _portHolder.get(addr.getSocketAddress());
    return true;
  }

  public String debugString() {
    StringBuilder buf = new StringBuilder("DBTCPConnector: ");
    if (_allHosts != null) buf.append("paired : ").append(_allHosts);
    else buf.append(_curAddress).append(" ").append(_curAddress._addr);

    return buf.toString();
  }

  DBObject isMasterCmd() {
    DBCollection collection = _mongo.getDB("admin").getCollection("$cmd");

    Iterator<DBObject> i = collection.__find(_isMaster, null, 0, 1, 0);
    if (i == null || !i.hasNext()) throw new MongoException("no result for ismaster query?");

    DBObject res = i.next();
    if (i.hasNext()) throw new MongoException("what's going on");

    return res;
  }

  boolean _isMaster(DBObject res) {
    Object x = res.get("ismaster");
    if (x == null) throw new IllegalStateException("ismaster shouldn't be null: " + res);

    if (x instanceof Boolean) return (Boolean) x;

    if (x instanceof Number) return ((Number) x).intValue() == 1;

    throw new IllegalArgumentException("invalid ismaster [" + x + "] : " + x.getClass().getName());
  }

  public void close() {
    _portHolder.close();
  }

  final Mongo _mongo;
  private ServerAddress _curAddress;
  private DBPortPool _curPortPool;
  private DBPortPool.Holder _portHolder;
  private final List<ServerAddress> _allHosts;

  private final ThreadLocal<MyPort> _threadPort =
      new ThreadLocal<MyPort>() {
        protected MyPort initialValue() {
          return new MyPort();
        }
      };

  private static final DBObject _isMaster = BasicDBObjectBuilder.start().add("ismaster", 1).get();
}