View Javadoc
1   // NAME
2   //      $RCSfile: SnmpContextPool.java,v $
3   // DESCRIPTION
4   //      [given below in javadoc format]
5   // DELTA
6   //      $Revision: 3.22 $
7   // CREATED
8   //      $Date: 2009/03/05 13:27:41 $
9   // COPYRIGHT
10  //      Westhawk Ltd
11  // TO DO
12  //
13  
14  /*
15   * Copyright (C) 2000 - 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 SNMP v1 contexts.
57   * This class reuses the existings contexts instead of creating a new
58   * one every time.
59   * <p>
60   * Every time a property changes the pool is checked for a SnmpContext
61   * context that matches all the new properties of this class. If no such
62   * context exists, a new one is made.
63   * The PDUs associated with the old context remain associated with the
64   * old context. 
65   * </p>
66   *
67   * <p>
68   * A counter indicates the number of times the context is referenced.
69   * The counter is decreased when <code>destroy()</code> is called. 
70   * When the counter reaches zero, the context is released.
71   * </p>
72   *
73   * <p>
74   * Note that because the underlying context can change when a property
75   * is changed and the PDUs remain associated with the old context, all 
76   * properties have to be set BEFORE a PDU is sent.
77   * </p>
78   *
79   * <p>
80   * Thanks to Seon Lee (slee@virtc.com) for reporting thread safety
81   * problems.
82   * </p>
83   *
84   * @see SnmpContext
85   * @see SnmpContextv2cPool
86   * @see SnmpContextv3Pool
87   *
88   * @author <a href="mailto:snmp@westhawk.co.uk">Birgit Arkesteijn</a>
89   * @version $Revision: 3.22 $ $Date: 2009/03/05 13:27:41 $
90   */
91  public class SnmpContextPool implements SnmpContextFace {
92      private static final String version_id = "@(#)$Id: SnmpContextPool.java,v 3.22 2009/03/05 13:27:41 birgita Exp $ Copyright Westhawk Ltd";
93  
94      protected static Hashtable contextPool;
95  
96      protected SnmpContext context = null;
97      protected String hostname, socketType, bindAddr;
98      protected int hostPort;
99      protected String community = SnmpContextFace.DEFAULT_COMMUNITY;
100 
101     /**
102      * Constructor, using the Standard socket.
103      *
104      * @param host The host to which the PDU will be sent
105      * @param port The port where the SNMP server will be
106      * @see SnmpContext#SnmpContext(String, int)
107      */
108     public SnmpContextPool(String host, int port) throws java.io.IOException {
109         this(host, port, SnmpContextFace.DEFAULT_COMMUNITY, null, STANDARD_SOCKET);
110     }
111 
112     /**
113      * Constructor.
114      * Parameter typeSocket should be either STANDARD_SOCKET, TCP_SOCKET or a
115      * fully qualified classname.
116      *
117      * @param host       The host to which the PDU will be sent
118      * @param port       The port where the SNMP server will be
119      * @param typeSocket The type of socket to use.
120      *
121      * @see SnmpContext#SnmpContext(String, int, String)
122      * @see SnmpContextBasisFace#STANDARD_SOCKET
123      * @see SnmpContextBasisFace#TCP_SOCKET
124      */
125     public SnmpContextPool(String host, int port, String typeSocket)
126             throws java.io.IOException {
127         this(host, port, SnmpContextFace.DEFAULT_COMMUNITY, null, typeSocket);
128     }
129 
130     /**
131      * Constructor.
132      * Parameter typeSocket should be either STANDARD_SOCKET, TCP_SOCKET or a
133      * fully qualified classname.
134      *
135      * @since 4_12
136      *
137      * @param host       The host to which the PDU will be sent
138      * @param port       The port where the SNMP server will be
139      * @param comm       The community name.
140      * @param typeSocket The type of socket to use.
141      *
142      * @see SnmpContextBasisFace#STANDARD_SOCKET
143      * @see SnmpContextBasisFace#TCP_SOCKET
144      */
145     public SnmpContextPool(String host, int port, String comm, String typeSocket)
146             throws java.io.IOException {
147         this(host, port, comm, null, typeSocket);
148     }
149 
150     /**
151      * Constructor.
152      * Parameter typeSocket should be either STANDARD_SOCKET, TCP_SOCKET or a
153      * fully qualified classname.
154      *
155      * @param host        The host to which the PDU will be sent
156      * @param port        The port where the SNMP server will be
157      * @param comm        The community name.
158      * @param bindAddress The local address the server will bind to
159      * @param typeSocket  The type of socket to use.
160      *
161      * @see SnmpContextBasisFace#STANDARD_SOCKET
162      * @see SnmpContextBasisFace#TCP_SOCKET
163      * @since 4_14
164      */
165     public SnmpContextPool(String host, int port, String comm, String bindAddress, String typeSocket)
166             throws java.io.IOException {
167         initPools();
168         hostname = host;
169         hostPort = port;
170         community = comm;
171         bindAddr = bindAddress;
172         socketType = typeSocket;
173 
174         context = getMatchingContext();
175     }
176 
177     private static synchronized void initPools() {
178         if (contextPool == null) {
179             contextPool = new Hashtable(5);
180         }
181     }
182 
183     public int getVersion() {
184         return SnmpConstants.SNMP_VERSION_1;
185     }
186 
187     public String getHost() {
188         return hostname;
189     }
190 
191     public int getPort() {
192         return hostPort;
193     }
194 
195     public String getBindAddress() {
196         return bindAddr;
197     }
198 
199     public String getTypeSocket() {
200         return socketType;
201     }
202 
203     public String getSendToHostAddress() {
204         String res = null;
205         if (context != null) {
206             res = context.getSendToHostAddress();
207         }
208         return res;
209     }
210 
211     public String getReceivedFromHostAddress() {
212         String res = null;
213         if (context != null) {
214             res = context.getReceivedFromHostAddress();
215         }
216         return res;
217     }
218 
219     public String getCommunity() {
220         return community;
221     }
222 
223     public void setCommunity(String newCommunity) {
224         if (newCommunity != null
225                 &&
226                 newCommunity.equals(community) == false) {
227             community = newCommunity;
228             try {
229                 context = getMatchingContext();
230             } catch (java.io.IOException exc) {
231             }
232         }
233     }
234 
235     public boolean addPdu(Pdu pdu)
236             throws java.io.IOException, PduException {
237         if (context == null) {
238             context = getMatchingContext();
239         }
240         return context.addPdu(pdu);
241     }
242 
243     public boolean removePdu(int requestId) {
244         boolean res = false;
245         if (context != null) {
246             res = context.removePdu(requestId);
247         }
248         return res;
249     }
250 
251     /**
252      * Encodes a PDU packet.
253      */
254     public byte[] encodePacket(byte msg_type, int rId, int errstat,
255             int errind, Enumeration ve, Object obj)
256             throws java.io.IOException, EncodingException {
257         byte[] res = null;
258         if (context != null) {
259             res = context.encodePacket(msg_type, rId, errstat, errind, ve,
260                     obj);
261         }
262         return res;
263     }
264 
265     public void sendPacket(byte[] packet) {
266         if (context != null) {
267             context.sendPacket(packet);
268         }
269     }
270 
271     /**
272      * Releases the resources held by this context. This method will
273      * decrement the reference counter. When the reference counter reaches
274      * zero the actual context is removed from the pool and destroyed.
275      */
276     public void destroy() {
277         synchronized (contextPool) {
278             if (context != null) {
279                 String hashKey = context.getHashKey();
280 
281                 int count = 0;
282                 SnmpContextPoolItem item = (SnmpContextPoolItem) contextPool.get(hashKey);
283                 if (item != null) {
284                     count = item.getCounter();
285                     count--;
286                     item.setCounter(count);
287                 }
288 
289                 if (count <= 0) {
290                     contextPool.remove(hashKey);
291                     context.destroy();
292                 }
293                 context = null;
294             }
295         }
296     }
297 
298     /**
299      * Destroys all the contexts (v1 and v2c) in the pool and empties the pool.
300      * The underlying implementation uses the same hashtable for both the v1
301      * and the v2c contexts.
302      *
303      * @see #destroy()
304      * @since 4_14
305      */
306     public void destroyPool() {
307         Hashtable copyOfPool = null;
308 
309         synchronized (contextPool) {
310             synchronized (contextPool) {
311                 copyOfPool = (Hashtable) contextPool.clone();
312             }
313             contextPool.clear();
314         }
315         context = null;
316 
317         Enumeration keys = copyOfPool.keys();
318         while (keys.hasMoreElements()) {
319             String key = (String) keys.nextElement();
320             SnmpContextPoolItem item = (SnmpContextPoolItem) copyOfPool.get(key);
321             if (item != null) {
322                 SnmpContextBasisFace cntxt = (SnmpContextBasisFace) item.getContext();
323                 cntxt.destroy();
324             }
325         }
326         copyOfPool.clear();
327     }
328 
329     public boolean isDestroyed() {
330         boolean isDestroyed = true;
331         if (context != null) {
332             isDestroyed = context.isDestroyed();
333         }
334         return isDestroyed;
335     }
336 
337     /**
338      * Returns a context from the pool.
339      * The pre-existing context (if there is any) is destroyed.
340      * This methods checks for an existing context that matches all our
341      * properties. If such a context does not exist, a new one is created and
342      * added to the pool.
343      *
344      * @return A context from the pool
345      * @see #getHashKey
346      */
347     protected SnmpContext getMatchingContext() throws java.io.IOException {
348         SnmpContextPoolItem item = null;
349         SnmpContext newContext = null;
350         String hashKey = getHashKey();
351 
352         destroy();
353         synchronized (contextPool) {
354             int count = 0;
355             if (contextPool.containsKey(hashKey)) {
356                 item = (SnmpContextPoolItem) contextPool.get(hashKey);
357                 newContext = (SnmpContext) item.getContext();
358                 count = item.getCounter();
359             } else {
360                 newContext = new SnmpContext(hostname, hostPort, bindAddr, socketType);
361                 newContext.setCommunity(community);
362                 item = new SnmpContextPoolItem(newContext);
363                 contextPool.put(hashKey, item);
364             }
365             count++;
366             item.setCounter(count);
367         }
368         return newContext;
369     }
370 
371     /**
372      * Dumps the pool of contexts. This is for debug purposes.
373      * 
374      * @param title The title of the dump
375      */
376     public void dumpContexts(String title) {
377         System.out.println(title + " " + contextPool.size() + " context(s)");
378         Enumeration keys = contextPool.keys();
379         int i = 0;
380         while (keys.hasMoreElements()) {
381             String key = (String) keys.nextElement();
382             SnmpContextPoolItem item = (SnmpContextPoolItem) contextPool.get(key);
383             if (item != null) {
384                 int count = item.getCounter();
385                 SnmpContext cntxt = (SnmpContext) item.getContext();
386 
387                 if (cntxt == context) {
388                     System.out.println("\tcurrent context: ");
389                 }
390                 System.out.println("\tcontext " + i + ": " + key + ", count: " + count
391                         + ", " + cntxt.toString() + "\n"
392                         + ", " + cntxt.getDebugString());
393                 i++;
394             }
395         }
396     }
397 
398     /**
399      * Returns the hash key. This key is built out of all properties. It
400      * serves as key for the hashtable of (v1) contexts.
401      *
402      * @return The hash key
403      */
404     public String getHashKey() {
405         String str = hostname
406                 + "_" + hostPort
407                 + "_" + bindAddr
408                 + "_" + socketType
409                 + "_" + community
410                 + "_v" + getVersion();
411         return str;
412     }
413 
414     /**
415      * Adds the specified trap listener. The listener will be added to the
416      * current context, <em>not</em> to all the contexts in the hashtable.
417      *
418      * @see SnmpContext#addTrapListener
419      */
420     public void addTrapListener(TrapListener l) throws java.io.IOException {
421         if (context != null) {
422             context.addTrapListener(l);
423         }
424     }
425 
426     /**
427      * Removes the specified trap listener. The listener will be removed
428      * from the current context, <em>not</em> from all the contexts in the
429      * hashtable.
430      *
431      * @see SnmpContext#removeTrapListener
432      */
433     public void removeTrapListener(TrapListener l) throws java.io.IOException {
434         if (context != null) {
435             context.removeTrapListener(l);
436         }
437     }
438 
439     public void addTrapListener(TrapListener l, int port) throws java.io.IOException {
440         if (context != null) {
441             context.addTrapListener(l, port);
442         }
443     }
444 
445     public void removeTrapListener(TrapListener l, int port) throws java.io.IOException {
446         if (context != null) {
447             context.removeTrapListener(l, port);
448         }
449     }
450 
451     public void addTrapListener(TrapListener l, ListeningContextPool lcontext) throws java.io.IOException {
452         if (context != null) {
453             context.addTrapListener(l, lcontext);
454         }
455     }
456 
457     public void removeTrapListener(TrapListener l, ListeningContextPool lcontext) throws java.io.IOException {
458         if (context != null) {
459             context.removeTrapListener(l, lcontext);
460         }
461     }
462 
463     public void addRequestPduListener(RequestPduListener l) throws java.io.IOException {
464         if (context != null) {
465             context.addRequestPduListener(l);
466         }
467     }
468 
469     public void removeRequestPduListener(RequestPduListener l) throws java.io.IOException {
470         if (context != null) {
471             context.removeRequestPduListener(l);
472         }
473     }
474 
475     public void addRequestPduListener(RequestPduListener l, int port) throws java.io.IOException {
476         if (context != null) {
477             context.addRequestPduListener(l, port);
478         }
479     }
480 
481     public void removeRequestPduListener(RequestPduListener l, int port) throws java.io.IOException {
482         if (context != null) {
483             context.removeRequestPduListener(l, port);
484         }
485     }
486 
487     public void addRequestPduListener(RequestPduListener l, ListeningContextPool lcontext) throws java.io.IOException {
488         if (context != null) {
489             context.addRequestPduListener(l, lcontext);
490         }
491     }
492 
493     public void removeRequestPduListener(RequestPduListener l, ListeningContextPool lcontext)
494             throws java.io.IOException {
495         if (context != null) {
496             context.removeRequestPduListener(l, lcontext);
497         }
498     }
499 
500     /**
501      * Processes the incoming PDU with the current context.
502      *
503      * @see SnmpContext#processIncomingPdu
504      */
505     public Pdu processIncomingPdu(byte[] message)
506             throws DecodingException, java.io.IOException {
507         Pdu pdu = null;
508         if (context != null) {
509             pdu = context.processIncomingPdu(message);
510         }
511         return pdu;
512     }
513 
514     /**
515      * Returns a string representation of the object.
516      * 
517      * @return The string
518      */
519     public String toString() {
520         String res = "";
521         if (context != null) {
522             res = context.toString();
523         }
524         return res;
525     }
526 
527     /**
528      * This method is not supported. It will throw a CloneNotSupportedException.
529      *
530      * @since 4_14
531      */
532     public Object clone() throws CloneNotSupportedException {
533         throw new CloneNotSupportedException();
534     }
535 
536 }