Class Files Representing Various LDAP Requests
[mono.git] / mcs / class / Novell.Directory.Ldap / Novell.Directory.Ldap / Message.cs
1 /******************************************************************************
2 * The MIT License
3 * Copyright (c) 2003 Novell Inc.  www.novell.com
4
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:
11
12 * The above copyright notice and this permission notice shall be included in 
13 * all copies or substantial portions of the Software.
14
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
21 * SOFTWARE.
22 *******************************************************************************/
23 //
24 // Novell.Directory.Ldap.Message.cs
25 //
26 // Author:
27 //   Sunil Kumar (Sunilk@novell.com)
28 //
29 // (C) 2003 Novell, Inc (http://www.novell.com)
30 //
31
32 using System;
33 using Novell.Directory.Ldap.Rfc2251;
34 using Novell.Directory.Ldap.Utilclass;
35
36 namespace Novell.Directory.Ldap
37 {
38         
39         /// <summary> Encapsulates an Ldap message, its state, and its replies.</summary>
40         /* package */
41         class Message
42         {
43                 private void  InitBlock()
44                 {
45                         replies = new MessageVector(5, 5);
46                 }
47                 /// <summary> Get number of messages queued.
48                 /// Don't count the last message containing result code.
49                 /// </summary>
50                 virtual internal int Count
51                 {
52                         /* package */
53                         
54                         get
55                         {
56                                 int size = replies.Count;
57                                 if (complete)
58                                 {
59                                         return (size > 0?(size - 1):size);
60                                 }
61                                 else
62                                 {
63                                         return size;
64                                 }
65                         }
66                         
67                 }
68
69                 /// <summary> sets the agent for this message</summary>
70                 virtual internal MessageAgent Agent
71                 {
72                         /* package */
73                         
74                         set
75                         {
76                                 this.agent = value;
77                                 return ;
78                         }
79                         
80                 }
81
82                 /// <summary> Returns true if replies are queued
83                 /// 
84                 /// </summary>
85                 /// <returns> false if no replies are queued, otherwise true
86                 /// </returns>
87                 /* package */
88                 internal virtual bool hasReplies()
89                 {
90                         if (replies == null)
91                         {
92                                 // abandoned request
93                                 return false;
94                         }
95                         return (replies.Count > 0);
96                 }
97
98                 virtual internal int MessageType
99                 {
100                         /* package */
101                         
102                         get
103                         {
104                                 if (msg == null)
105                                 {
106                                         return - 1;
107                                 }
108                                 return msg.Type;
109                         }
110                         
111                 }
112
113                 virtual internal int MessageID
114                 {
115                         /* package */
116                         
117                         get
118                         {
119                                 return msgId;
120                         }
121                         
122                 }
123
124                 /// <summary> gets the operation complete status for this message
125                 /// 
126                 /// </summary>
127                 /// <returns> the true if the operation is complete, i.e.
128                 /// the LdapResult has been received.
129                 /// </returns>
130                 virtual internal bool Complete
131                 {
132                         /* package */
133                         
134                         get
135                         {
136                                 return complete;
137                         }
138                         
139                 }
140
141                 /// <summary> Gets the next reply from the reply queue or waits until one is there
142                 /// 
143                 /// </summary>
144                 /// <returns> the next reply message on the reply queue or null
145                 /// </returns>
146                 /* package */
147                 internal virtual System.Object waitForReply()
148                 {
149                         if (replies == null)
150                         {
151                                 return null;
152                         }
153                         // sync on message so don't confuse with timer thread
154                         lock (replies)
155                         {
156                                 System.Object msg = null;
157                                 while (waitForReply_Renamed_Field)
158                                 {
159                                         if ((replies.Count == 0))
160                                         {
161                                                 try
162                                                 {
163                                                         System.Threading.Monitor.Wait(replies);
164                                                 }
165                                                 catch (System.Threading.ThreadInterruptedException ir)
166                                                 {
167                                                         ; // do nothing
168                                                 }
169                                                 if (waitForReply_Renamed_Field)
170                                                 {
171                                                         continue;
172                                                 }
173                                                 else
174                                                 {
175                                                         break;
176                                                 }
177                                         }
178                                         else
179                                         {
180                                                 System.Object temp_object;
181                                                 temp_object = replies[0];
182                                                 replies.RemoveAt(0);
183                                                 msg = temp_object; // Atomic get and remove
184                                         }
185                                         if ((complete || !acceptReplies) && (replies.Count == 0))
186                                         {
187                                                 // Remove msg from connection queue when last reply read
188                                                 conn.removeMessage(this);
189                                         }
190                                         else
191                                         {
192                                         }
193                                         return msg;
194                                 }
195                                 return null;
196                         }
197                 }
198                 
199
200                 /// <summary> Gets the next reply from the reply queue if one exists
201                 /// 
202                 /// </summary>
203                 /// <returns> the next reply message on the reply queue or null if none
204                 /// </returns>
205                 virtual internal System.Object Reply
206                 {
207                         /* package */
208                         
209                         get
210                         {
211                                 System.Object msg;
212                                 if (replies == null)
213                                 {
214                                         return null;
215                                 }
216                                 lock (replies)
217                                 {
218                                         // Test and remove must be atomic
219                                         if ((replies.Count == 0))
220                                         {
221                                                 return null; // No data
222                                         }
223                                         System.Object temp_object;
224                                         temp_object = replies[0];
225                                         replies.RemoveAt(0);
226                                         msg = temp_object; // Atomic get and remove
227                                 }
228                                 if ((conn != null) && (complete || !acceptReplies) && (replies.Count == 0))
229                                 {
230                                         // Remove msg from connection queue when last reply read
231                                         conn.removeMessage(this);
232                                 }
233                                 return msg;
234                         }
235                         
236                 }
237
238                 /// <summary> Returns true if replies are accepted for this request.
239                 /// 
240                 /// </summary>
241                 /// <returns> false if replies are no longer accepted for this request
242                 /// </returns>
243                 /* package */
244                 internal virtual bool acceptsReplies()
245                 {
246                         return acceptReplies;
247                 }
248
249                 /// <summary> gets the LdapMessage request associated with this message
250                 /// 
251                 /// </summary>
252                 /// <returns> the LdapMessage request associated with this message
253                 /// </returns>
254                 virtual internal LdapMessage Request
255                 {
256                         /*package*/
257                         
258                         get
259                         {
260                                 return msg;
261                         }
262                         
263                 }
264
265
266                 virtual internal bool BindRequest
267                 {
268                         /* package */
269                         
270                         get
271                         {
272                                 return (bindprops != null);
273                         }
274                         
275                 }
276
277
278                 /// <summary> gets the MessageAgent associated with this message
279                 /// 
280                 /// </summary>
281                 /// <returns> the MessageAgent associated with this message
282                 /// </returns>
283                 virtual internal MessageAgent MessageAgent
284                 {
285                         /* package */
286                         
287                         get
288                         {
289                                 return agent;
290                         }
291                         
292                 }
293
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
308                 
309                 internal Message(LdapMessage msg, int mslimit, Connection conn, MessageAgent agent, LdapMessageQueue queue, BindProperties bindprops)
310                 {
311                         InitBlock();
312                         this.msg = msg;
313                         this.conn = conn;
314                         this.agent = agent;
315                         this.queue = queue;
316                         this.mslimit = mslimit;
317                         this.msgId = msg.MessageID;
318                         this.bindprops = bindprops;
319                         return ;
320                 }
321
322                 internal void  sendMessage()
323                 {
324                         conn.writeMessage(this);
325                         // Start the timer thread
326                         if (mslimit != 0)
327                         {
328                                 // Don't start the timer thread for abandon or Unbind
329                                 switch (msg.Type)
330                                 {
331                                         
332                                         case LdapMessage.ABANDON_REQUEST: 
333                                         case LdapMessage.UNBIND_REQUEST: 
334                                                 mslimit = 0;
335                                                 break;
336                                         
337                                         default: 
338                                                 timer = new Timeout(this, mslimit, this);
339                                                 timer.IsBackground = true; // If this is the last thread running, allow exit.
340                                                 timer.Start();
341                                                 break;
342                                         
343                                 }
344                         }
345                         return ;
346                 }
347
348                 internal virtual void  Abandon(LdapConstraints cons, InterThreadException informUserEx)
349                 {
350                         if (!waitForReply_Renamed_Field)
351                         {
352                                 return ;
353                         }
354                         acceptReplies = false; // don't listen to anyone
355                         waitForReply_Renamed_Field = false; // don't let sleeping threads lie
356                         if (!complete)
357                         {
358                                 try
359                                 {
360                                         // If a bind, release bind semaphore & wake up waiting threads
361                                         // Must do before writing abandon message, otherwise deadlock
362                                         if (bindprops != null)
363                                         {
364                                                 int id;
365                                                 if (conn.BindSemIdClear)
366                                                 {
367                                                         // Semaphore id for normal operations
368                                                         id = msgId;
369                                                 }
370                                                 else
371                                                 {
372                                                         // Semaphore id for sasl bind
373                                                         id = conn.BindSemId;
374                                                         conn.clearBindSemId();
375                                                 }
376                                                 conn.freeWriteSemaphore(id);
377                                         }
378                                         
379                                         // Create the abandon message, but don't track it.
380                                         LdapControl[] cont = null;
381                                         if (cons != null)
382                                         {
383                                                 cont = cons.getControls();
384                                         }
385                                         LdapMessage msg = new LdapAbandonRequest(msgId, cont);
386                                         // Send abandon message to server
387                                         conn.writeMessage(msg);
388                                 }
389                                 catch (LdapException ex)
390                                 {
391                                         ; // do nothing
392                                 }
393                                 // If not informing user, remove message from agent
394                                 if (informUserEx == null)
395                                 {
396                                         agent.Abandon(msgId, null);
397                                 }
398                                 conn.removeMessage(this);
399                         }
400                         // Get rid of all replies queued
401                         if (informUserEx != null)
402                         {
403                                 replies.Add(new LdapResponse(informUserEx, conn.ActiveReferral));
404                                 stopTimer();
405                                 // wake up waiting threads to receive exception
406                                 sleepersAwake();
407                                 // Message will get cleaned up when last response removed from queue
408                         }
409                         else
410                         {
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
414                                 sleepersAwake();
415                                 cleanup();
416                         }
417                         return ;
418                 }
419
420                 private void  cleanup()
421                 {
422                         stopTimer(); // Make sure timer stopped
423                         try
424                         {
425                                 acceptReplies = false;
426                                 if (conn != null)
427                                 {
428                                         conn.removeMessage(this);
429                                 }
430                                 // Empty out any accumuluated replies
431                                 if (replies != null)
432                                 {
433                                         while (!(replies.Count == 0))
434                                         {
435                                                 System.Object temp_object;
436                                                 temp_object = replies[0];
437                                                 replies.RemoveAt(0);
438                                                 System.Object generatedAux = temp_object;
439                                         }
440                                 }
441                         }
442                         catch (System.Exception ex)
443                         {
444                                 // nothing
445                         }
446                         // Let GC clean up this stuff, leave name in case finalized is called
447                         conn = null;
448                         msg = null;
449                         // agent = null;  // leave this reference
450                         queue = null;
451                         //replies = null; //leave this since we use it as a semaphore
452                         bindprops = null;
453                         return ;
454                 }
455                 
456                 ~Message()
457                 {
458                         cleanup();
459                         return ;
460                 }
461
462
463                 internal virtual void  putReply(RfcLdapMessage message)
464                 {
465                         if (!acceptReplies)
466                         {
467                                 return ;
468                         }
469                         replies.Add(message);
470                         message.RequestingMessage = msg; // Save request message info
471                         switch (message.Type)
472                         {
473                                 
474                                 case LdapMessage.SEARCH_RESPONSE: 
475                                 case LdapMessage.SEARCH_RESULT_REFERENCE: 
476                                         break;
477                                 
478                                 default: 
479                                         int res;
480                                         stopTimer();
481                                         // Accept no more results for this message
482                                         // Leave on connection queue so we can abandon if necessary
483                                         acceptReplies = false;
484                                         complete = true;
485                                         if (bindprops != null)
486                                         {
487                                                 res = ((RfcResponse) message.Response).getResultCode().intValue();
488                                                 if (res == LdapException.SASL_BIND_IN_PROGRESS)
489                                                 {
490                                                 }
491                                                 else
492                                                 {
493                                                         // We either have success or failure on the bind
494                                                         if (res == LdapException.SUCCESS)
495                                                         {
496                                                                 // Set bind properties into connection object
497                                                                 conn.BindProperties = bindprops;
498                                                         }
499                                                         else
500                                                         {
501                                                         }
502                                                         // If not a sasl bind in-progress, release the bind
503                                                         // semaphore and wake up all waiting threads
504                                                         int id;
505                                                         if (conn.BindSemIdClear)
506                                                         {
507                                                                 // Semaphore id for normal operations
508                                                                 id = msgId;
509                                                         }
510                                                         else
511                                                         {
512                                                                 // Semaphore id for sasl bind
513                                                                 id = conn.BindSemId;
514                                                                 conn.clearBindSemId();
515                                                         }
516                                                         conn.freeWriteSemaphore(id);
517                                                 }
518                                         }
519                                         break;
520                                 
521                         }
522                         // wake up waiting threads
523                         sleepersAwake();
524                         return ;
525                 }
526                 
527                 /// <summary> stops the timeout timer from running</summary>
528                 /* package */
529                 internal virtual void  stopTimer()
530                 {
531                         // If timer thread started, stop it
532                         if (timer != null)
533                         {
534                                 timer.Interrupt();
535                         }
536                         return ;
537                 }
538
539                 /// <summary> Notifies all waiting threads</summary>
540                 private void  sleepersAwake()
541                 {
542                         // Notify any thread waiting for this message id
543                         lock (replies)
544                         {
545                                 System.Threading.Monitor.Pulse(replies);
546                         }
547                         // Notify a thread waiting for any message id
548                         agent.sleepersAwake(false);
549                         return ;
550                 }
551
552                 /// <summary> Timer class to provide timing for messages.  Only called
553                 /// if time to wait is non zero.
554                 /// </summary>
555                 private sealed class Timeout:SupportClass.ThreadClass
556                 {
557                         private void  InitBlock(Message enclosingInstance)
558                         {
559                                 this.enclosingInstance = enclosingInstance;
560                         }
561                         private Message enclosingInstance;
562                         public Message Enclosing_Instance
563                         {
564                                 get
565                                 {
566                                         return enclosingInstance;
567                                 }
568                                 
569                         }
570                         private int timeToWait = 0;
571                         private Message message;
572                         
573                         /* package */
574                         internal Timeout(Message enclosingInstance, int interval, Message msg):base()
575                         {
576                                 InitBlock(enclosingInstance);
577                                 timeToWait = interval;
578                                 message = msg;
579                                 return ;
580                         }
581                         
582                         /// <summary> The timeout thread.  If it wakes from the sleep, future input
583                         /// is stopped and the request is timed out.
584                         /// </summary>
585                         override public void  Run()
586                         {
587                                 try
588                                 {
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));
593                                 }
594                                 catch (System.Threading.ThreadInterruptedException ie)
595                                 {
596                                         // the timer was stopped, do nothing
597                                 }
598                                 return ;
599                         }
600                 }
601
602                 /// <summary> sets the agent for this message</summary>
603
604         }
605 }