/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package netscape.ldap; import java.util.Vector; /** * A queue of response messsages from the server. Multiple requests * can be multiplexed on the same queue. For synchronous LDAPConnection * requests, there will be only one request per queue. For asynchronous * LDAPConnection requests, the user can add multiple request to the * same queue. * * Superclass for LDAResponseListener and LDAPSearchListener * */ class LDAPMessageQueue implements java.io.Serializable { static final long serialVersionUID = -7163312406176592278L; /** * Request entry encapsulates request parameters */ private static class RequestEntry { int id; LDAPConnection connection; LDAPConnThread connThread; long timeToComplete; RequestEntry(int id, LDAPConnection connection, LDAPConnThread connThread, int timeLimit) { this.id= id; this.connection = connection; this.connThread = connThread; this.timeToComplete = (timeLimit == 0) ? Long.MAX_VALUE : (System.currentTimeMillis() + timeLimit); } } /** * Internal variables */ private /*LDAPMessage */ Vector m_messageQueue = new Vector(1); private /*RequestEntry*/ Vector m_requestList = new Vector(1); private LDAPException m_exception; /* For network errors */ private boolean m_asynchOp; // A flag whether there are time constrained requests private boolean m_timeConstrained; /** * Constructor * @param asynchOp a boolean flag that is true if the object is used * for asynchronous LDAP operations * @see netscape.ldap.LDAPAsynchronousConnection */ LDAPMessageQueue (boolean asynchOp) { m_asynchOp = asynchOp; } /** * Returns a flag whether the listener is used for asynchronous LDAP * operations * @return asynchronous operation flag. * @see netscape.ldap.LDAPAsynchronousConnection */ boolean isAsynchOp() { return m_asynchOp; } /** * Blocks until a response is available. * Used by LDAPConnection.sendRequest (synch ops) to test if the server * is really available after a request had been sent. * @exception LDAPException Network error exception * @exception LDAPInterruptedException The invoking thread was interrupted */ synchronized void waitFirstMessage () throws LDAPException { while(m_requestList.size() != 0 && m_exception == null && m_messageQueue.size() == 0) { waitForMessage(); } // Network exception occurred ? if (m_exception != null) { LDAPException ex = m_exception; m_exception = null; throw ex; } } /** * Blocks until a response is available or until all operations * associated with the object have completed or been canceled. * @return LDAP message or null if there are no more outstanding requests. * @exception LDAPException Network error exception * @exception LDAPInterruptedException The invoking thread was interrupted */ synchronized LDAPMessage nextMessage () throws LDAPException { while(m_requestList.size() != 0 && m_exception == null && m_messageQueue.size() == 0) { waitForMessage(); } // Network exception occurred ? if (m_exception != null) { LDAPException ex = m_exception; m_exception = null; throw ex; } // Are there any outstanding requests left if (m_requestList.size() == 0) { return null; // No outstanding requests } // Dequeue the first entry LDAPMessage msg = (LDAPMessage) m_messageQueue.elementAt(0); m_messageQueue.removeElementAt(0); // Is the ldap operation completed? if (msg instanceof LDAPResponse) { removeRequest(msg.getMessageID()); } return msg; } /** * Wait for request to complete. This method blocks until a message of * type LDAPResponse has been received. Used by synchronous search * with batch size of zero (block until all results are received) * @return LDAPResponse message or null if there are no more outstanding requests. * @exception LDAPException Network error exception * @exception LDAPInterruptedException The invoking thread was interrupted */ synchronized LDAPResponse completeRequest () throws LDAPException { while (true) { while(m_requestList.size() != 0 && m_exception == null && m_messageQueue.size() == 0) { waitForMessage(); } // Network exception occurred ? if (m_exception != null) { LDAPException ex = m_exception; m_exception = null; throw ex; } // Are there any outstanding requests left? if (m_requestList.size() == 0) { return null; // No outstanding requests } // Search an instance of LDAPResponse for (int i= m_messageQueue.size()-1; i >=0; i--) { LDAPMessage msg = (LDAPMessage) m_messageQueue.elementAt(i); if (msg instanceof LDAPResponse) { // Dequeue the entry and return m_messageQueue.removeElementAt(i); return (LDAPResponse)msg; } } // Not found, wait for the next message waitForMessage(); } } /** * Wait for a response message. Process interrupts and honor * time limit if set for any request */ synchronized private void waitForMessage () throws LDAPException{ if (!m_timeConstrained) { try { wait (); return; } catch (InterruptedException e) { throw new LDAPInterruptedException("Interrupted LDAP operation"); } } /** * Perform time constrained wait */ long minTimeToComplete = Long.MAX_VALUE; long now = System.currentTimeMillis(); for (int i=0; i < m_requestList.size(); i++) { RequestEntry entry = (RequestEntry)m_requestList.elementAt(i); // time limit exceeded ? if (entry.timeToComplete <= now) { entry.connection.abandon(entry.id); throw new LDAPException("Time to complete operation exceeded", LDAPException.LDAP_TIMEOUT); } if (entry.timeToComplete < minTimeToComplete) { minTimeToComplete = entry.timeToComplete; } } long timeLimit = (minTimeToComplete == Long.MAX_VALUE)? 0 :(minTimeToComplete - now); try { m_timeConstrained = (timeLimit != 0); wait (timeLimit); } catch (InterruptedException e) { throw new LDAPInterruptedException("Interrupted LDAP operation"); } } /** * Merge two message queues. * Move/append the content from another message queue to this one. * * To be used for synchronization of asynchronous LDAP operations where * requests are sent by one thread but processed by another one * * A client may be implemented in such a way that one thread makes LDAP * requests and calls l.getMessageIDs(), while another thread is * responsible for * processing of responses (call l.getResponse()). Both threads are using * the same listener objects. In such a case, a race * condition may occur, where a LDAP response message is retrieved and * the request terminated (request ID removed) before the first thread * has a chance to execute l.getMessageIDs(). * The proper way to handle this scenario is to create a separate listener * for each new request, and after l.getMessageIDs() has been invoked, * merge the * new request with the existing one. * @param mq2 message queue to merge with this one */ void merge(LDAPMessageQueue mq2) { // Yield just in case the LDAPConnThread is in the process of // dispatching a message Thread.yield(); synchronized(this) { synchronized (mq2) { for (int i=0; i < mq2.m_messageQueue.size(); i++) { m_messageQueue.addElement(mq2.m_messageQueue.elementAt(i)); } if (mq2.m_exception != null) { m_exception = mq2.m_exception; } for (int i=0; i < mq2.m_requestList.size(); i++) { RequestEntry entry = (RequestEntry)mq2.m_requestList.elementAt(i); m_requestList.addElement(entry); // Notify LDAPConnThread to redirect mq2 designated responses to this mq entry.connThread.changeListener(entry.id, this); } mq2.reset(); notifyAll(); // notify for mq2 } notifyAll(); // notify this mq } } /** * Retrieves all messages currently in the queue without blocking * @return vector of messages. */ synchronized Vector getAllMessages() { Vector result = m_messageQueue; m_messageQueue = new Vector(1); return result; } /** * Queues the LDAP server's response. This causes anyone waiting * in nextMessage() to unblock. * @param msg response message */ synchronized void addMessage (LDAPMessage msg) { m_messageQueue.addElement(msg); // Mark conn as bound for asych bind operations if (isAsynchOp() && msg.getType() == msg.BIND_RESPONSE) { if (((LDAPResponse) msg).getResultCode() == 0) { getConnection(msg.getMessageID()).setBound(true); } } notifyAll (); } /** * Signals that a network exception occured while servicing the * request. This exception will be throw to any thread waiting * in nextMessage() * @param connThread LDAPConnThread on which the exception occurred * @param e exception */ synchronized void setException (LDAPConnThread connThread, LDAPException e) { m_exception = e; removeAllRequests(connThread); notifyAll (); } /** * Checks if response message is received. * @return true or false. */ boolean isMessageReceived() { return m_messageQueue.size() != 0; } /** * Returns the count of queued messages * @return message count. */ public int getMessageCount () { return m_messageQueue.size(); } /** * Remove all queued messages associated with the request ID * Called when a LDAP operation is abandoned * * Not synchronized as its private and can be called only by * abandon() and removeAllRequests() * * @return count of removed messages. */ private int removeAllMessages(int id) { int removeCount=0; for (int i=(m_messageQueue.size()-1); i>=0; i--) { LDAPMessage msg = (LDAPMessage)m_messageQueue.elementAt(i); if (msg.getMessageID() == id) { m_messageQueue.removeElementAt(i); removeCount++; } } return removeCount; } /** * Resets the state of this object, so it can be recycled. * Used by LDAPConnection synchronous operations. * @see netscape.ldap.LDAPConnection#getResponseListener * @see netscape.ldap.LDAPConnection#getSearchListener */ void reset () { m_exception = null; m_messageQueue.removeAllElements(); m_requestList.removeAllElements(); m_timeConstrained = false; } /** * Returns the connection associated with the specified request id * @param id request id * @return connection. */ synchronized LDAPConnection getConnection(int id) { for (int i=0; i < m_requestList.size(); i++) { RequestEntry entry = (RequestEntry)m_requestList.elementAt(i); if (id == entry.id) { return entry.connection; } } return null; } /** * Returns the connection thread associated with the specified request id * @param id request id. * @return connection thread. */ synchronized LDAPConnThread getConnThread(int id) { for (int i=0; i < m_requestList.size(); i++) { RequestEntry entry = (RequestEntry)m_requestList.elementAt(i); if (id == entry.id) { return entry.connThread; } } return null; } /** * Returns message ID of the last request * @return message ID. */ synchronized int getMessageID() { int reqCnt = m_requestList.size(); if ( reqCnt == 0) { return -1; } else { RequestEntry entry = (RequestEntry)m_requestList.elementAt(reqCnt-1); return entry.id; } } /** * Returns a list of message IDs for all outstanding requests * @return message ID array. */ synchronized int[] getMessageIDs() { int[] ids = new int[m_requestList.size()]; for (int i=0; i < ids.length; i++) { RequestEntry entry = (RequestEntry)m_requestList.elementAt(i); ids[i] = entry.id; } return ids; } /** * Registers a LDAP request * @param id LDAP request message ID * @param connection LDAP Connection for the message ID * @param connThread a physical connection to the server * @param timeLimit the maximum number of milliseconds to wait for * the request to complete */ synchronized void addRequest(int id, LDAPConnection connection, LDAPConnThread connThread, int timeLimit) { m_requestList.addElement(new RequestEntry(id, connection, connThread, timeLimit)); if (timeLimit != 0) { m_timeConstrained = true; } notifyAll(); } /** * Returns the number of outstanding requests. * @return outstanding request count. */ public int getRequestCount() { return m_requestList.size(); } /** * Remove request with the specified ID * Called when a LDAP operation is abandoned (called from * LDAPConnThread), or terminated (called by nextMessage() when * LDAPResponse message is received) * @return flag indicating whether the request was removed. */ synchronized boolean removeRequest(int id) { for (int i=0; i < m_requestList.size(); i++) { RequestEntry entry = (RequestEntry)m_requestList.elementAt(i); if (id == entry.id) { m_requestList.removeElementAt(i); removeAllMessages(id); notifyAll(); return true; } } return false; } /** * Remove all requests associated with the specified connThread * Called when a connThread has a network error * @return number of removed requests. */ synchronized int removeAllRequests(LDAPConnThread connThread) { int removeCount=0; for (int i=(m_requestList.size()-1); i>=0; i--) { RequestEntry entry = (RequestEntry)m_requestList.elementAt(i); if (connThread == entry.connThread) { m_requestList.removeElementAt(i); removeCount++; // remove all queued messages as well removeAllMessages(entry.id); } } notifyAll(); return removeCount; } /** * String representation of the object */ public String toString() { StringBuffer sb = new StringBuffer("LDAPMessageQueue:"); sb.append(" requestIDs={"); for (int i=0; i < m_requestList.size(); i++) { if (i>0) { sb.append(","); } sb.append(((RequestEntry)m_requestList.elementAt(i)).id); } sb.append("} messageCount="+m_messageQueue.size()); return sb.toString(); } }