Source code: com/presumo/mobileagent/Agent.java
1 /**
2 * This file is part of Presumo.
3 *
4 * Presumo is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * Presumo is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Presumo; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 *
19 * Copyright (c) 2001 Dan Greff
20 */
21 package com.presumo.mobileagent;
22
23 import com.presumo.util.log.Logger;
24 import com.presumo.util.log.LoggerFactory;
25
26 import java.io.BufferedReader;
27 import java.io.InputStreamReader;
28 import java.io.Serializable;
29
30 import java.util.Vector;
31
32 import javax.jms.ExceptionListener;
33 import javax.jms.Topic;
34 import javax.jms.TopicConnection;
35 import javax.jms.TopicSession;
36 import javax.jms.TopicSubscriber;
37 import javax.jms.TopicPublisher;
38 import javax.jms.Message;
39 import javax.jms.MessageListener;
40 import javax.jms.ObjectMessage;
41 import javax.jms.JMSException;
42 import javax.jms.Session;
43
44
45 /**
46 * A very rough base class for mobile agents thrown together
47 * to support Presumo's test framework.
48 * <p>
49 * Currently your Agent implementation must be in the CLASSPATH
50 * of the JVM your agent moves to.
51 * <p>
52 * <note>
53 * It would be relativly trivial to have the Agent run
54 * against a ClassLoader running over JMS to remove this restriction.
55 * If you are reading this with interest in using MobileAgents
56 * over JMS contact the project admins at www.presumo.com and
57 * request the feature, or (if you are capable of doing so)
58 * volunteer to do it yourself and join the project. ;) DTG
59 * </note>
60 *
61 * @author Dan Greff
62 */
63 public abstract class Agent implements Serializable, Runnable
64 {
65 /** Topic name for which all agent JMS traffic runs across. **/
66 public static final String AGENT_TOPIC = "MobileAgentAdmin";
67
68 /** Message property name for Runner targets a Agent is sent to. **/
69 public static final String RUNNER_PROP = "RunnerTargets";
70
71 /** Message property name used to indicate a Runner identifier. **/
72 public static final String RUNNER_NAME = "RunnerName";
73
74 /** Message property name used to indicate the type of message. **/
75 public static final String MESSAGE_TYPE = "AgentMessageType";
76
77 public static final int AGENT_MOVE = 0;
78 public static final int AGENT_START = 1;
79 public static final int AGENT_STOP = 2;
80 public static final int RUNNER_QUERY = 3;
81 public static final int QUERY_RESPONSE = 4;
82
83
84 /** Means by which the agent communicates witht he JMS layer **/
85 protected transient TopicConnection connx;
86 protected transient TopicSession agentSession;
87 private transient Topic agentTopic;
88
89 /////////////////////////////////////////////////////////////////////////
90 // Constructors //
91 /////////////////////////////////////////////////////////////////////////
92
93 /**
94 * Default constructor needed only for deserialization. When subclassing
95 * agent you must make sure <code>agentSession</code> is set.
96 */
97 public Agent()
98 {
99 logger.entry("Agent");
100 logger.exit("Agent", this);
101 }
102
103 /**
104 * Preferred Constructor to invoke when subclassing. If you do not invoke
105 * this Constructor you must ensure that <code>agentSession</code> is set.
106 */
107 public Agent(TopicConnection connx)
108 {
109 logger.entry("Agent", connx);
110 setConnection(connx);
111 logger.exit("Agent", this);
112 }
113
114 ///////////////////////////////////////////////////////////////////////////
115 // Public Methods //
116 ///////////////////////////////////////////////////////////////////////////
117
118 /**
119 * Move the agent to the specified <code>AgentRunner</code>.
120 */
121 public void moveTo(String runnerName) throws JMSException
122 {
123 logger.entry("moveTo", runnerName);
124 moveTo(new String [] { runnerName });
125 logger.exit("moveTo");
126 }
127
128 /**
129 * Broadcast the agent to all the specivied <code>AgentRunner</code>.
130 */
131 public synchronized void moveTo(String [] runnerNames)
132 throws JMSException
133 {
134 logger.entry("moveTo", runnerNames);
135 TopicPublisher publisher = agentSession.createPublisher(agentTopic);
136 try {
137 Message msg = createRunnerMoveMessage(runnerNames);
138 publisher.publish(msg);
139 } finally {
140 publisher.close();
141 }
142 logger.exit("moveTo");
143 }
144
145
146 /**
147 * Get all <code>AgentRunner</code>'s available to run Agents.
148 */
149 public synchronized String [] getAvailableRunners(long timeout)
150 throws JMSException
151 {
152 logger.entry("getAvailableRunners", new Long(timeout));
153
154 TopicSubscriber sub = null;
155 TopicPublisher pub = null;
156 Vector runners = new Vector();
157
158 try {
159 sub = createQueryResponseReceiver();
160 pub = agentSession.createPublisher(agentTopic);
161 pub.publish( createQueryMessage() );
162
163
164 long start = System.currentTimeMillis();
165 long diff = 0;
166 while(diff < timeout) {
167 Message msg = sub.receive(timeout - diff);
168 if (msg != null) {
169 String name = (String) msg.getObjectProperty(RUNNER_NAME);
170 logger.debug("----> Found runner: " + name);
171 if (name != null && !runners.contains(name)) {
172 runners.add(name);
173 }
174 }
175 diff = System.currentTimeMillis() - start;
176 }
177 } finally {
178 if (sub != null) sub.close();
179 if (pub != null) pub.close();
180 }
181
182 String [] retval = new String[runners.size()];
183 runners.toArray(retval);
184
185 logger.exit("getAvailableRunners", retval);
186 return retval;
187 }
188
189 /**
190 * Used by the AgentRunner to set the Agent's TopicConnection after it is
191 * deserialized.
192 */
193 public void setConnection(TopicConnection connx)
194 {
195 logger.entry("setConnection", connx);
196
197 try {
198 this.connx = connx;
199 agentSession = connx.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
200 agentTopic = agentSession.createTopic(AGENT_TOPIC);
201 } catch (JMSException jmsex) {
202 logger.exception(jmsex);
203 }
204
205 logger.exit("setConnection");
206 }
207
208 /**
209 * Implement this method if you want your agent to do special processing
210 * in response to a start broadcast. If you want your processing to
211 * start as soon as the agent is moved simply perform all work when the
212 * run() method is called.
213 *
214 * <note>Do not do much processing in the thread calling this method.
215 * instead a spin lock should be used to wake up thre Thread executing
216 * the run method.
217 * </note>
218 */
219 public abstract void startAgent();
220
221 /**
222 * Implement this method if you want your agent to do something when
223 * a stop is broadcast.
224 *
225 * <note>Implementations should cause the Thread executing your run()
226 * implementation to finish and return from the method gracefully.
227 * </note>
228 */
229 public abstract void stopAgent();
230
231
232 public abstract void runAgent();
233
234 public final void run()
235 {
236 logger.entry("run");
237
238 runAgent();
239
240 if (agentSession != null) {
241 try {
242 agentSession.close();
243 } catch (JMSException jmsex) {
244 logger.exception(jmsex);
245 }
246 }
247
248 logger.exit("run");
249 }
250 ///////////////////////////////////////////////////////////////////////////
251 // Package Methods //
252 ///////////////////////////////////////////////////////////////////////////
253 // None //
254 //////////
255
256 ///////////////////////////////////////////////////////////////////////////
257 // Protected Methods //
258 ///////////////////////////////////////////////////////////////////////////
259
260 /**
261 * Encapsulates the creation of a message to move the Agent instance.
262 *
263 * @param runnerTargets All targets for which the Agent is destined.
264 *
265 * @return A JmsMessage containing a serialized instance of <code>this</code>
266 * Agent instance, and all necessary message properties to ensure
267 * the message is correctly received by all <code>AgentRunners</code>
268 */
269 protected Message createRunnerMoveMessage(String [] runnerTargets)
270 throws JMSException
271 {
272 logger.entry("createRunnerMoveMessage", runnerTargets);
273
274 StringBuffer buf = new StringBuffer();
275 for (int i=0; i < runnerTargets.length; ++i) {
276 buf.append(runnerTargets[i]);
277 if (i < (runnerTargets.length-1)) buf.append(';');
278 }
279
280 ObjectMessage msg = agentSession.createObjectMessage(this);
281 msg.setIntProperty(MESSAGE_TYPE, AGENT_MOVE);
282 msg.setStringProperty(RUNNER_PROP, buf.toString());
283
284 logger.exit("createRunnerMoveMessage", msg);
285 return msg;
286 }
287
288
289 /**
290 * Encapsulates the creation of a message sent to request all runners to
291 * send their names.
292 */
293 protected Message createQueryMessage() throws JMSException
294 {
295 logger.entry("createQueryMessage");
296
297 Message msg = agentSession.createMessage();
298 msg.setIntProperty(MESSAGE_TYPE, RUNNER_QUERY);
299
300 logger.exit("createQueryMessage", msg);
301 return msg;
302 }
303
304
305 /**
306 * Encapsulates creating a subscriber with the necessary filters to
307 * ensure retrieval of all responses to the query.
308 */
309 protected TopicSubscriber createQueryResponseReceiver() throws JMSException
310 {
311 logger.entry("createQueryResponseReceiver");
312
313 StringBuffer filter = new StringBuffer();
314 filter.append(MESSAGE_TYPE);
315 filter.append("=");
316 filter.append(QUERY_RESPONSE);
317
318 TopicSubscriber sub =
319 agentSession.createSubscriber(agentTopic, filter.toString(), false);
320
321 logger.exit("createqueryResponseReceiver", sub);
322 return sub;
323 }
324
325
326
327 ////////////////////////////// Misc stuff ////////////////////////////////
328
329 private static Logger logger = LoggerFactory.getLogger(Agent.class, null);
330
331 ///////////////////////////////////////////////////////////////////////////
332
333 }