1 /******************************************************************************
3 * Copyright (c) 2003 Novell Inc. www.novell.com
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the Software), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 *******************************************************************************/
24 // Novell.Directory.Ldap.Message.cs
27 // Sunil Kumar (Sunilk@novell.com)
29 // (C) 2003 Novell, Inc (http://www.novell.com)
33 using Novell.Directory.Ldap.Rfc2251;
34 using Novell.Directory.Ldap.Utilclass;
36 namespace Novell.Directory.Ldap
39 /// <summary> Encapsulates an Ldap message, its state, and its replies.</summary>
43 private void InitBlock()
45 replies = new MessageVector(5, 5);
47 /// <summary> Get number of messages queued.
48 /// Don't count the last message containing result code.
50 virtual internal int Count
56 int size = replies.Count;
59 return (size > 0?(size - 1):size);
69 /// <summary> sets the agent for this message</summary>
70 virtual internal MessageAgent Agent
82 /// <summary> Returns true if replies are queued
85 /// <returns> false if no replies are queued, otherwise true
88 internal virtual bool hasReplies()
95 return (replies.Count > 0);
98 virtual internal int MessageType
113 virtual internal int MessageID
124 /// <summary> gets the operation complete status for this message
127 /// <returns> the true if the operation is complete, i.e.
128 /// the LdapResult has been received.
130 virtual internal bool Complete
141 /// <summary> Gets the next reply from the reply queue or waits until one is there
144 /// <returns> the next reply message on the reply queue or null
147 internal virtual System.Object waitForReply()
153 // sync on message so don't confuse with timer thread
156 System.Object msg = null;
157 while (waitForReply_Renamed_Field)
159 if ((replies.Count == 0))
163 System.Threading.Monitor.Wait(replies);
165 catch (System.Threading.ThreadInterruptedException ir)
169 if (waitForReply_Renamed_Field)
180 System.Object temp_object;
181 temp_object = replies[0];
183 msg = temp_object; // Atomic get and remove
185 if ((complete || !acceptReplies) && (replies.Count == 0))
187 // Remove msg from connection queue when last reply read
188 conn.removeMessage(this);
200 /// <summary> Gets the next reply from the reply queue if one exists
203 /// <returns> the next reply message on the reply queue or null if none
205 virtual internal System.Object Reply
218 // Test and remove must be atomic
219 if ((replies.Count == 0))
221 return null; // No data
223 System.Object temp_object;
224 temp_object = replies[0];
226 msg = temp_object; // Atomic get and remove
228 if ((conn != null) && (complete || !acceptReplies) && (replies.Count == 0))
230 // Remove msg from connection queue when last reply read
231 conn.removeMessage(this);
238 /// <summary> Returns true if replies are accepted for this request.
241 /// <returns> false if replies are no longer accepted for this request
244 internal virtual bool acceptsReplies()
246 return acceptReplies;
249 /// <summary> gets the LdapMessage request associated with this message
252 /// <returns> the LdapMessage request associated with this message
254 virtual internal LdapMessage Request
266 virtual internal bool BindRequest
272 return (bindprops != null);
278 /// <summary> gets the MessageAgent associated with this message
281 /// <returns> the MessageAgent associated with this message
283 virtual internal MessageAgent MessageAgent
294 private LdapMessage msg; // msg request sent to server
295 private Connection conn; // Connection object where msg sent
296 private MessageAgent agent; // MessageAgent handling this request
297 private LdapMessageQueue queue; // Application message queue
298 private int mslimit; // client time limit in milliseconds
299 private SupportClass.ThreadClass timer = null; // Timeout thread
300 // Note: MessageVector is synchronized
301 private MessageVector replies; // place to store replies
302 private int msgId; // message ID of this request
303 private bool acceptReplies = true; // false if no longer accepting replies
304 private bool waitForReply_Renamed_Field = true; // true if wait for reply
305 private bool complete = false; // true LdapResult received
306 private System.String name; // String name used for Debug
307 private BindProperties bindprops; // Bind properties if a bind request
309 internal Message(LdapMessage msg, int mslimit, Connection conn, MessageAgent agent, LdapMessageQueue queue, BindProperties bindprops)
316 this.mslimit = mslimit;
317 this.msgId = msg.MessageID;
318 this.bindprops = bindprops;
322 internal void sendMessage()
324 conn.writeMessage(this);
325 // Start the timer thread
328 // Don't start the timer thread for abandon or Unbind
332 case LdapMessage.ABANDON_REQUEST:
333 case LdapMessage.UNBIND_REQUEST:
338 timer = new Timeout(this, mslimit, this);
339 timer.IsBackground = true; // If this is the last thread running, allow exit.
348 internal virtual void Abandon(LdapConstraints cons, InterThreadException informUserEx)
350 if (!waitForReply_Renamed_Field)
354 acceptReplies = false; // don't listen to anyone
355 waitForReply_Renamed_Field = false; // don't let sleeping threads lie
360 // If a bind, release bind semaphore & wake up waiting threads
361 // Must do before writing abandon message, otherwise deadlock
362 if (bindprops != null)
365 if (conn.BindSemIdClear)
367 // Semaphore id for normal operations
372 // Semaphore id for sasl bind
374 conn.clearBindSemId();
376 conn.freeWriteSemaphore(id);
379 // Create the abandon message, but don't track it.
380 LdapControl[] cont = null;
383 cont = cons.getControls();
385 LdapMessage msg = new LdapAbandonRequest(msgId, cont);
386 // Send abandon message to server
387 conn.writeMessage(msg);
389 catch (LdapException ex)
393 // If not informing user, remove message from agent
394 if (informUserEx == null)
396 agent.Abandon(msgId, null);
398 conn.removeMessage(this);
400 // Get rid of all replies queued
401 if (informUserEx != null)
403 replies.Add(new LdapResponse(informUserEx, conn.ActiveReferral));
405 // wake up waiting threads to receive exception
407 // Message will get cleaned up when last response removed from queue
411 // Wake up any waiting threads, so they can terminate.
412 // If informing the user, we wake sleepers after
413 // caller queues dummy response with error status
420 private void cleanup()
422 stopTimer(); // Make sure timer stopped
425 acceptReplies = false;
428 conn.removeMessage(this);
430 // Empty out any accumuluated replies
433 while (!(replies.Count == 0))
435 System.Object temp_object;
436 temp_object = replies[0];
438 System.Object generatedAux = temp_object;
442 catch (System.Exception ex)
446 // Let GC clean up this stuff, leave name in case finalized is called
449 // agent = null; // leave this reference
451 //replies = null; //leave this since we use it as a semaphore
463 internal virtual void putReply(RfcLdapMessage message)
469 replies.Add(message);
470 message.RequestingMessage = msg; // Save request message info
471 switch (message.Type)
474 case LdapMessage.SEARCH_RESPONSE:
475 case LdapMessage.SEARCH_RESULT_REFERENCE:
481 // Accept no more results for this message
482 // Leave on connection queue so we can abandon if necessary
483 acceptReplies = false;
485 if (bindprops != null)
487 res = ((RfcResponse) message.Response).getResultCode().intValue();
488 if (res == LdapException.SASL_BIND_IN_PROGRESS)
493 // We either have success or failure on the bind
494 if (res == LdapException.SUCCESS)
496 // Set bind properties into connection object
497 conn.BindProperties = bindprops;
502 // If not a sasl bind in-progress, release the bind
503 // semaphore and wake up all waiting threads
505 if (conn.BindSemIdClear)
507 // Semaphore id for normal operations
512 // Semaphore id for sasl bind
514 conn.clearBindSemId();
516 conn.freeWriteSemaphore(id);
522 // wake up waiting threads
527 /// <summary> stops the timeout timer from running</summary>
529 internal virtual void stopTimer()
531 // If timer thread started, stop it
539 /// <summary> Notifies all waiting threads</summary>
540 private void sleepersAwake()
542 // Notify any thread waiting for this message id
545 System.Threading.Monitor.Pulse(replies);
547 // Notify a thread waiting for any message id
548 agent.sleepersAwake(false);
552 /// <summary> Timer class to provide timing for messages. Only called
553 /// if time to wait is non zero.
555 private sealed class Timeout:SupportClass.ThreadClass
557 private void InitBlock(Message enclosingInstance)
559 this.enclosingInstance = enclosingInstance;
561 private Message enclosingInstance;
562 public Message Enclosing_Instance
566 return enclosingInstance;
570 private int timeToWait = 0;
571 private Message message;
574 internal Timeout(Message enclosingInstance, int interval, Message msg):base()
576 InitBlock(enclosingInstance);
577 timeToWait = interval;
582 /// <summary> The timeout thread. If it wakes from the sleep, future input
583 /// is stopped and the request is timed out.
585 override public void Run()
589 System.Threading.Thread.Sleep(new System.TimeSpan(10000 * timeToWait));
590 message.acceptReplies = false;
591 // Note: Abandon clears the bind semaphore after failed bind.
592 message.Abandon(null, new InterThreadException("Client request timed out", null, LdapException.Ldap_TIMEOUT, null, message));
594 catch (System.Threading.ThreadInterruptedException ie)
596 // the timer was stopped, do nothing
602 /// <summary> sets the agent for this message</summary>