Source code: org/enhydra/servlet/debug/DebugManager.java
1 /*
2 * Enhydra Java Application Server Project
3 *
4 * The contents of this file are subject to the Enhydra Public License
5 * Version 1.1 (the "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy of the License on
7 * the Enhydra web site ( http://www.enhydra.org/ ).
8 *
9 * Software distributed under the License is distributed on an "AS IS"
10 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 * the License for the specific terms governing rights and limitations
12 * under the License.
13 *
14 * The Initial Developer of the Enhydra Application Server is Lutris
15 * Technologies, Inc. The Enhydra Application Server and portions created
16 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17 * All Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * $Id: DebugManager.java,v 1.4.4.1 2000/10/19 17:59:10 jasona Exp $
22 */
23
24 package org.enhydra.servlet.debug;
25
26 import org.enhydra.servlet.connectionMethods.*;
27 import com.lutris.multiServer.*;
28 import org.enhydra.servlet.ServletContainer;
29 import org.enhydra.servlet.filter.*;
30
31
32 /**
33 * Servlet debug manager for monitoring servlet I/O. An instance of this
34 * object monitors a single servlet instance. A fixed-sized queue of servlet
35 * transaction records is maintained. As a transaction is completed by the
36 * servlet, a record is added to the end of the queue. If the queue has
37 * reached its maximum size, an element is removed from the head of the
38 * queue. Applications wishing to monitor servlet transactions registers
39 * with this object and is called when objects are added or removed from
40 * the queue. Calling on removal is necessary as one of the main goals of
41 * this class is to support presentations that display a list of transaction
42 * records. A unique id number is associated with each transaction record.
43 * The transaction record ids are sequencially allocated, one with a large
44 * number occured after one with a smaller number.
45 *
46 * @version $Revision: 1.4.4.1 $
47 * @author Mark Diekhans
48 * @since 2.0
49 */
50 public class DebugManager {
51
52 /**
53 * Servlet being managed.
54 */
55 private String servletId;
56
57 /**
58 * Current monitoring state.
59 */
60 private boolean enabled = false;
61
62 /**
63 * Queue of transaction records. This is protected so that subclasses
64 * may access the queue.
65 */
66 protected ServletRecordQueue recordQueue;
67
68 /**
69 * List of objects to call on servlet debug events. Kept as an array
70 * since addition/removal of callbacks is infrequence and the expected
71 * number is small. The array may not be full.
72 */
73 private ServletRecordCallback[] callbacks =
74 new ServletRecordCallback[1];
75
76 /**
77 * Number of valid entries in callbacks.
78 */
79 private int numCallbacks = 0;
80
81 /**
82 * Filter that is used by this DebugManager.
83 */
84 private ServletRecorderFilter filter;
85 private String filterID;
86
87 /**
88 * Construct an new servlet debug manager and associate it with a servlet.
89 * Servlet monitoring is initially disabled.
90 *
91 * @param servletId symbolic servlet identifier of the servlet to manage.
92 * @param queueSize maximum number of servlet transaction records that
93 * can be queued at a given time.
94 * @param saveResponseData Should we save a copy of the data written
95 * to the socket for each transaction? Could use up alot of memory.
96 */
97 public DebugManager(String servletId,
98 int queueSize,
99 boolean saveResponseData) {
100 this.servletId = servletId;
101 recordQueue = new ServletRecordQueue(queueSize);
102 filter = new ServletRecorderFilter(this, saveResponseData, servletId);
103 }
104
105
106
107 /**
108 * Enable recording servlet transactions. This adds a filter to the
109 * filter manager, and then adds the filter to all channels that
110 * map to the servlet.
111 *
112 * @exception ConnectionMethodException If there is an error getting
113 * the connection methods and channels, or adding the filter.
114 */
115 public synchronized void enable() throws ConnectionMethodException {
116 ServletContainer sc = (ServletContainer)
117 MultiServer.getService("ServletContainer");
118 ConnectionMethodManager cmm = sc.getConnectionMethodManager();
119 FilterManager fm = sc.getFilterManager();
120 String[] connectionIds = cmm.getIDs();
121 /*
122 * Add to thr filter manager. We must delete it later when
123 * debugging is disabled. Because the name starts with an
124 * underscore, it will not be displayed in the admin app.
125 */
126 filterID = fm.getUniqueID("_debug_" + servletId);
127 fm.add(filterID, filter, "internal debugging");
128 for (int cmi = 0; cmi < connectionIds.length; cmi++) {
129 ConnectionMethod method = cmm.get(connectionIds[cmi]);
130 String[] channelIds = method.getChannelIDs();
131 for (int ch = 0; ch < channelIds.length; ch++) {
132 String chanServletId =
133 method.getChannelStatus(channelIds[ch]).servletID;
134 if (servletId.equals(chanServletId)) {
135 method.addTransactionFilter(channelIds[ch], filterID);
136 }
137 }
138 }
139 enabled = true;
140 }
141
142 /**
143 * Disable recording servlet transactions. This removes the filter from
144 * all the channels, then removes the filter from the filter manager.
145 *
146 * @exception ConnectionMethodException If there is an error getting
147 * the connection methods and channels, or adding the filter.
148 */
149 public synchronized void disable() throws ConnectionMethodException {
150 ServletContainer sc = (ServletContainer)
151 MultiServer.getService("ServletContainer");
152 ConnectionMethodManager cmm = sc.getConnectionMethodManager();
153 String[] connectionIds = cmm.getIDs();
154
155 for (int cmi = 0; cmi < connectionIds.length; cmi++) {
156 ConnectionMethod method = cmm.get(connectionIds[cmi]);
157 String[] channelIds = method.getChannelIDs();
158 for (int ch = 0; ch < channelIds.length; ch++) {
159 String[] filterIds =
160 method.getChannelStatus(channelIds[ch]).filterIDs;
161 for (int fi = 0; fi < filterIds.length; fi++) {
162 if (filterIds[fi].equals(filterID)) {
163 method.removeTransactionFilter(channelIds[ch],
164 filterID);
165 }
166 }
167 }
168 }
169 /*
170 * Delete the filter from the filter manager.
171 */
172 sc.getFilterManager().delete(filterID);
173 enabled = false;
174 }
175
176 /**
177 * Determine if servlets monitoring is enabled.
178 *
179 * @return <CODE>true</CODE> if enabled, <CODE>false</CODE> if disabled.
180 */
181 public boolean isEnabled() {
182 return enabled;
183 }
184
185 /**
186 * Get the servlet associated with this object.
187 *
188 * @return the servlet id.
189 */
190 public String getServlet() {
191 return servletId;
192 }
193
194 /**
195 * Registered to be called when a servlet transaction is queued.
196 *
197 * @param callback The object to invoke when a transaction object
198 * is available.
199 * @param catchUp Pass in true if you want an initial series of
200 * <CODE>transactionRecordAddEvent()</CODE> calls, reflecting the
201 * current set of transactions stored in the queue.
202 * Wanring: if this is set to true, and there are transactions in the
203 * queue, then <CODE>transactionRecordAddEvent()</CODE> will be
204 * called before this function returns.
205 */
206 public synchronized void addTransactionCallback(
207 ServletRecordCallback callback, boolean catchUp) {
208 if (numCallbacks == callbacks.length) {
209 ServletRecordCallback[] newTbl =
210 new ServletRecordCallback[callbacks.length * 2];
211 System.arraycopy(callbacks, 0, newTbl, 0, callbacks.length);
212 callbacks = newTbl;
213 }
214 callbacks[numCallbacks] = callback;
215 numCallbacks++;
216
217 /*
218 * Should we inform the callee of the current set of transactions?
219 */
220 if (catchUp)
221 recordQueue.dumpContents(callback);
222 }
223
224 /**
225 * Remove transaction callback that was created with
226 * <CODE>addTransactionCallback</CODE>.
227 *
228 * @param callback object to delete from callback list.
229 */
230 public synchronized void removeTranactionCallback(
231 ServletRecordCallback callback) {
232 int i;
233 for (i = 0; i < numCallbacks; i++) {
234 if (callbacks[i] == callback) {
235 break;
236 }
237 }
238 // Compact. Array bounds violation nails bad callback.
239 callbacks[i] = callbacks[numCallbacks - 1];
240 numCallbacks--;
241 }
242
243 /**
244 * Find a servlet transaction record by id.
245 *
246 * @param transactionId unique number identifying the desired transaction
247 * record.
248 * @return a transaction record or <CODE>null</CODE> if the transaction
249 * is no longer in the queue.
250 */
251 public synchronized ServletTransactionRecord getTransactionRecord(
252 int transactionId) {
253 return recordQueue.findById(transactionId);
254 }
255
256 /**
257 * Add a servlet transaction to the end of the queue. Use by the
258 * servlet filters.
259 *
260 * @param transactionRecord the record to add to the queue.
261 */
262 protected synchronized void addTransactionRecord(
263 ServletTransactionRecord transactionRecord) {
264 ServletTransactionRecord deletedRecord =
265 recordQueue.addRecord(transactionRecord);
266
267 for (int i = 0; i < numCallbacks; i++) {
268 callbacks[i].transactionRecordAddEvent(transactionRecord,
269 deletedRecord);
270 }
271 }
272
273 /**
274 * Clear all records from the queue.
275 */
276 public synchronized void clearRecordQueue() {
277 recordQueue.clear();
278 for (int i = 0; i < numCallbacks; i++) {
279 callbacks[i].clearRecordsEvent();
280 }
281 }
282 }