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); }
/** * 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); } } }
/** * 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; } } }
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); }
static { m_logger = Logger.getLogger((Class) Dummy.class); }
static { m_logger = Logger.getLogger((Class) SearchTreasureOccupationStartMessage.class); }
/** * 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; } } }
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(); } } }
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(); }