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