View Javadoc
1   // NAME
2   //      $RCSfile: ListeningContextPool.java,v $
3   // DESCRIPTION
4   //      [given below in javadoc format]
5   // DELTA
6   //      $Revision: 3.7 $
7   // CREATED
8   //      $Date: 2009/03/05 13:27:41 $
9   // COPYRIGHT
10  //      Westhawk Ltd
11  // TO DO
12  //
13  
14  /*
15   * Copyright (C) 2005 - 2006 by Westhawk Ltd
16   * <a href="www.westhawk.co.uk">www.westhawk.co.uk</a>
17   *
18   * Permission to use, copy, modify, and distribute this software
19   * for any purpose and without fee is hereby granted, provided
20   * that the above copyright notices appear in all copies and that
21   * both the copyright notice and this permission notice appear in
22   * supporting documentation.
23   * This software is provided "as is" without express or implied
24   * warranty.
25   * author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
26   */
27  
28  package uk.co.westhawk.snmp.stack;
29  
30  /*-
31   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
32   * SNMP Java Client
33   * ჻჻჻჻჻჻
34   * Copyright 2023 MetricsHub, Westhawk
35   * ჻჻჻჻჻჻
36   * This program is free software: you can redistribute it and/or modify
37   * it under the terms of the GNU Lesser General Public License as
38   * published by the Free Software Foundation, either version 3 of the
39   * License, or (at your option) any later version.
40   *
41   * This program is distributed in the hope that it will be useful,
42   * but WITHOUT ANY WARRANTY; without even the implied warranty of
43   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
44   * GNU General Lesser Public License for more details.
45   *
46   * You should have received a copy of the GNU General Lesser Public
47   * License along with this program.  If not, see
48   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
49   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
50   */
51  
52  import java.util.*;
53  import uk.co.westhawk.snmp.event.*;
54  
55  /**
56   * This class contains the pool of listening contexts. The usage of this
57   * class will prevent more than one ListeningContext trying to listen to
58   * the same port.
59   *
60   * @see ListeningContext
61   * @since 4_14
62   * @author <a href="mailto:snmp@westhawk.co.uk">Birgit Arkesteijn</a>
63   * @version $Revision: 3.7 $ $Date: 2009/03/05 13:27:41 $
64   */
65  public class ListeningContextPool implements ListeningContextFace {
66      private static final String version_id = "@(#)$Id: ListeningContextPool.java,v 3.7 2009/03/05 13:27:41 birgita Exp $ Copyright Westhawk Ltd";
67  
68      protected static Hashtable contextPool;
69  
70      protected ListeningContext context = null;
71      protected String socketType;
72      protected String bindAddr;
73      protected int hostPort;
74  
75      /**
76       * Constructor, using the Standard socket type.
77       *
78       * @param port The local port where packets are received
79       *
80       * @see #ListeningContextPool(int, String)
81       * @see SnmpContextBasisFace#STANDARD_SOCKET
82       */
83      public ListeningContextPool(int port) {
84          this(port, null, SnmpContextBasisFace.STANDARD_SOCKET);
85      }
86  
87      /**
88       * Constructor, using the Standard socket type.
89       *
90       * If bindAddress is null, it will accept connections on
91       * any/all local addresses. If you want to listen to
92       * <ul>
93       * <li>
94       * IPv4 only interfaces, use address "0.0.0.0"
95       * </li>
96       * <li>
97       * IPv6 only interfaces, use address "::"
98       * </li>
99       * </ul>
100      *
101      * @param port        The local port where packets are received
102      * @param bindAddress The local address the server will bind to
103      *
104      * @see SnmpContextBasisFace#STANDARD_SOCKET
105      */
106     public ListeningContextPool(int port, String bindAddress) {
107         this(port, bindAddress, SnmpContextBasisFace.STANDARD_SOCKET);
108     }
109 
110     /**
111      * Constructor.
112      *
113      * If bindAddress is null, it will accept connections on
114      * any/all local addresses. If you want to listen to
115      * <ul>
116      * <li>
117      * IPv4 only interfaces, use address "0.0.0.0"
118      * </li>
119      * <li>
120      * IPv6 only interfaces, use address "::"
121      * </li>
122      * </ul>
123      *
124      * The typeSocket will indicate which type of socket to use. This way
125      * different handlers can be provided.
126      *
127      * <p>
128      * Note, the TCP_SOCKET does not provide functionality to send a
129      * response back. Listening on such a socket is only useful when
130      * listening for traps.
131      * </p>
132      *
133      * @param port        The local port where packets are received
134      * @param bindAddress The local address the server will bind to
135      * @param typeSocket  The type of socket to use.
136      *
137      * @see SnmpContextBasisFace#STANDARD_SOCKET
138      * @see SnmpContextBasisFace#TCP_SOCKET
139      */
140     public ListeningContextPool(int port, String bindAddress, String typeSocket) {
141         initPools();
142         hostPort = port;
143         bindAddr = bindAddress;
144         socketType = typeSocket;
145 
146         context = getMatchingContext();
147     }
148 
149     private static synchronized void initPools() {
150         if (contextPool == null) {
151             contextPool = new Hashtable(5);
152         }
153     }
154 
155     public int getPort() {
156         return hostPort;
157     }
158 
159     public String getBindAddress() {
160         return bindAddr;
161     }
162 
163     public String getTypeSocket() {
164         return socketType;
165     }
166 
167     public int getMaxRecvSize() {
168         int res = SnmpContextBasisFace.MSS;
169         if (context != null) {
170             res = context.getMaxRecvSize();
171         }
172         return res;
173     }
174 
175     /**
176      * Sets the maximum number of bytes this context will read from the
177      * socket. By default this will be set to <code>MSS</code> (i.e. 1300).
178      * Only the current context will be affected, <em>not</em> to all the
179      * contexts in the pool.
180      *
181      * @param no The new size
182      *
183      * @see SnmpContextBasisFace#MSS
184      * @see AbstractSnmpContext#getMaxRecvSize()
185      */
186     public void setMaxRecvSize(int no) {
187         if (context == null) {
188             context = getMatchingContext();
189         }
190         context.setMaxRecvSize(no);
191     }
192 
193     /**
194      * Destroys the current context.
195      *
196      * <p>
197      * Note that by calling this method the whole stack will stop listening
198      * for packets on the port this context was listening on! The listeners
199      * added via the SnmpContext classes are affected as well.
200      * </p>
201      *
202      * @see #destroyPool()
203      * @see ListeningContextPool#destroy()
204      */
205     public void destroy() {
206         synchronized (contextPool) {
207             if (context != null) {
208                 String hashKey = context.getHashKey();
209 
210                 int count = 0;
211                 Item item = (Item) contextPool.get(hashKey);
212 
213                 if (item != null) {
214                     count = item.getCounter();
215                     count--;
216                     item.setCounter(count);
217                 }
218 
219                 if (count <= 0) {
220                     contextPool.remove(hashKey);
221                     context.destroy();
222                 }
223                 context = null;
224             }
225         }
226     }
227 
228     /**
229      * Destroys all the contexts in the pool and empties the pool.
230      *
231      * <p>
232      * Note that by calling this method the whole stack will stop listening
233      * for any packets! The listeners added via the
234      * SnmpContext classes are affected as well.
235      * </p>
236      *
237      * @see #destroy()
238      */
239     public void destroyPool() {
240         Hashtable copyOfPool = null;
241 
242         synchronized (contextPool) {
243             synchronized (contextPool) {
244                 copyOfPool = (Hashtable) contextPool.clone();
245             }
246             contextPool.clear();
247         }
248         context = null;
249 
250         Enumeration keys = copyOfPool.keys();
251         while (keys.hasMoreElements()) {
252             String key = (String) keys.nextElement();
253             Item item = (Item) copyOfPool.get(key);
254             if (item != null) {
255                 ListeningContext cntxt = (ListeningContext) item.getContext();
256                 cntxt.destroy();
257             }
258         }
259         copyOfPool.clear();
260     }
261 
262     /**
263      * Returns a context from the pool.
264      * This methods checks for an existing context that matches all our
265      * properties. If such a context does not exist, a new one is created and
266      * added to the pool.
267      *
268      * @return A context from the pool
269      * @see #getHashKey
270      */
271     protected ListeningContext getMatchingContext() {
272         Item item = null;
273         ListeningContext newContext = null;
274         String hashKey = getHashKey();
275 
276         destroy();
277         synchronized (contextPool) {
278             int count = 0;
279             if (contextPool.containsKey(hashKey)) {
280                 item = (Item) contextPool.get(hashKey);
281                 newContext = item.getContext();
282                 count = item.getCounter();
283             } else {
284                 newContext = new ListeningContext(hostPort, bindAddr, socketType);
285                 item = new Item(newContext);
286                 contextPool.put(hashKey, item);
287             }
288             count++;
289             item.setCounter(count);
290         }
291         return newContext;
292     }
293 
294     /**
295      * Dumps the pool of contexts. This is for debug purposes.
296      * 
297      * @param title The title of the dump
298      */
299     public void dumpContexts(String title) {
300         Hashtable copyOfPool = null;
301         synchronized (contextPool) {
302             copyOfPool = (Hashtable) contextPool.clone();
303         }
304 
305         System.out.println(title + " " + copyOfPool.size());
306         Enumeration keys = copyOfPool.keys();
307         int i = 0;
308         while (keys.hasMoreElements()) {
309             String key = (String) keys.nextElement();
310             Item item = (Item) copyOfPool.get(key);
311 
312             if (item != null) {
313                 int count = item.getCounter();
314                 ListeningContext cntxt = item.getContext();
315 
316                 System.out.println("\tcontext: " + key + ", count: " + count
317                         + ", index: " + i + ", " + cntxt.toString());
318                 if (cntxt == context) {
319                     System.out.println("\t\tcurrent context");
320                 }
321                 i++;
322             }
323         }
324     }
325 
326     /**
327      * Returns the hash key. This key is built out of all properties. It
328      * serves as key for the pool of contexts.
329      *
330      * @return The hash key
331      */
332     public String getHashKey() {
333         String str = hostPort
334                 + "_" + bindAddr
335                 + "_" + socketType;
336         return str;
337     }
338 
339     public void addRawPduListener(RawPduListener l)
340             throws java.io.IOException {
341         if (context != null) {
342             context.addRawPduListener(l);
343         }
344     }
345 
346     public void removeRawPduListener(RawPduListener l) {
347         if (context != null) {
348             context.removeRawPduListener(l);
349         }
350     }
351 
352     /**
353      * Removes the specified PDU listener from all the contexts in the pool.
354      *
355      * @see ListeningContext#removeRawPduListener
356      */
357     public void removeRawPduListenerFromPool(RawPduListener l) {
358         Hashtable copyOfPool = null;
359 
360         if (contextPool != null) {
361             synchronized (contextPool) {
362                 copyOfPool = (Hashtable) contextPool.clone();
363             }
364 
365             Enumeration keys = copyOfPool.keys();
366             while (keys.hasMoreElements()) {
367                 String key = (String) keys.nextElement();
368                 Item item = (Item) copyOfPool.get(key);
369 
370                 if (item != null) {
371                     ListeningContext cntxt = item.getContext();
372                     cntxt.removeRawPduListener(l);
373                 }
374             }
375         }
376     }
377 
378     public void addUnhandledRawPduListener(RawPduListener l)
379             throws java.io.IOException {
380         if (context != null) {
381             context.addUnhandledRawPduListener(l);
382         }
383     }
384 
385     public void removeUnhandledRawPduListener(RawPduListener l) {
386         if (context != null) {
387             context.removeUnhandledRawPduListener(l);
388         }
389     }
390 
391     /**
392      * Returns a string representation of the object.
393      * 
394      * @return The string
395      */
396     public String toString() {
397         String res = "";
398         if (context != null) {
399             res = context.toString();
400         }
401         return res;
402     }
403 
404     class Item {
405         private ListeningContext context = null;
406         private int counter = 0;
407 
408         /**
409          * Constructor.
410          *
411          * @param con The context
412          */
413         Item(ListeningContext con) {
414             context = con;
415             counter = 0;
416         }
417 
418         ListeningContext getContext() {
419             return context;
420         }
421 
422         int getCounter() {
423             return counter;
424         }
425 
426         void setCounter(int i) {
427             counter = i;
428         }
429 
430         /**
431          * Returns a string representation of the object.
432          * 
433          * @return The string
434          */
435         public String toString() {
436             StringBuffer buffer = new StringBuffer("Item[");
437             buffer.append("context=").append(context.toString());
438             buffer.append(", counter=").append(counter);
439             buffer.append("]");
440             return buffer.toString();
441         }
442     } // end Item
443 
444 } // end ListeningContextPool