1 /*------------------------------------------------------------------------------
2 Name: XmlScriptAccess.java
3 Project: xmlBlaster.org
4 Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
5 Comment: Bean to export with Windows ActiveX bridge
6 ------------------------------------------------------------------------------*/
7 package org.xmlBlaster.client.activex;
8
9 import java.io.ByteArrayOutputStream;
10 import java.io.StringReader;
11 import java.io.OutputStream;
12 import java.io.Reader;
13 import java.util.Properties;
14 import java.beans.SimpleBeanInfo;
15
16 import EDU.oswego.cs.dl.util.concurrent.Latch; // http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
17
18 import java.util.logging.Logger;
19 import java.util.logging.Level;
20 import org.xmlBlaster.util.Global;
21 import org.xmlBlaster.util.XmlBlasterException;
22 import org.xmlBlaster.util.def.ErrorCode;
23 import org.xmlBlaster.client.script.XmlScriptClient;
24 import org.xmlBlaster.client.script.XmlScriptInterpreter;
25 import org.xmlBlaster.client.I_Callback;
26 import org.xmlBlaster.client.SynchronousCache;
27 import org.xmlBlaster.client.protocol.I_CallbackServer;
28 import org.xmlBlaster.client.qos.ConnectQos;
29 import org.xmlBlaster.client.qos.ConnectReturnQos;
30 import org.xmlBlaster.client.qos.DisconnectQos;
31 import org.xmlBlaster.client.key.UpdateKey;
32 import org.xmlBlaster.client.qos.UpdateQos;
33 import org.xmlBlaster.client.key.GetKey;
34 import org.xmlBlaster.client.key.SubscribeKey;
35 import org.xmlBlaster.client.key.UnSubscribeKey;
36 import org.xmlBlaster.client.key.EraseKey;
37 import org.xmlBlaster.client.qos.GetQos;
38 import org.xmlBlaster.client.qos.PublishReturnQos;
39 import org.xmlBlaster.client.qos.SubscribeQos;
40 import org.xmlBlaster.client.qos.SubscribeReturnQos;
41 import org.xmlBlaster.client.qos.EraseQos;
42 import org.xmlBlaster.client.qos.EraseReturnQos;
43 import org.xmlBlaster.client.qos.UnSubscribeQos;
44 import org.xmlBlaster.client.qos.UnSubscribeReturnQos;
45 import org.xmlBlaster.util.MsgUnit;
46
47 /**
48 * This bean can be exported to a Microsoft dll (ActiveX component) and
49 * be accessed by C# or Visual Basic.Net
50 * <p>
51 * Here we support XML scripting access as described in the <i>client.script</i> requirement
52 * by calling <code>sendRequest()</code> or alternatively you can use the methods
53 * like <code>publishStr()</code> or <code>subscribe()</code> directly. The latter
54 * methods have the advantage to return a ready parsed object to the ActiveX component,
55 * for example Visual Basic can directly call all methods of <code>SubscribeReturnQos</code>
56 * which is returned by <code>subscribe()</code>.
57 * </p>
58 * <p>
59 * Compile the ActiveX control with <code>build activex</code> and see Visual Basic
60 * and C# samples in directory <code>xmlBlaster/demo/activex</code>.
61 * </p>
62 * <p>
63 * As events into ActiveX can't have a return value and can't throw
64 * an exception back to us we handle it here as a callback, for example
65 * Visual Basic needs to call <code>sendUpdateReturn()</code> or <code>sendUpdateException()</code> after
66 * processing a message received by <code>update()</code>.
67 * Our update thread blocks until one of those two methods is called, however
68 * the blocking times out after 10 minutes which is adjustable with
69 * the property <code>client/activex/responseWaitTime</code> given in milli seconds.
70 * </p>
71 * <p>
72 * One instance of this can hold one permanent connection to the xmlBlaster server,
73 * multi threaded access is supported.
74 * </p>
75 *
76 * @see <a href="http://www.xmlblaster.org/xmlBlaster/doc/requirements/client.script.html">client.script requirement</a>
77 * @see <a href="http://java.sun.com/j2se/1.4.2/docs/guide/beans/axbridge/developerguide/index.html">ActiveX Bridge Developer Guide</a>
78 * @author <a href="mailto:xmlBlaster@marcelruff.info">Marcel Ruff</a>
79 */
80 public class XmlScriptAccess extends SimpleBeanInfo implements I_Callback {
81 private static String ME = "XmlScriptAccess";
82 private final Global glob;
83 private static Logger log = Logger.getLogger(XmlScriptAccess.class.getName());
84 private XmlScriptInterpreter interpreter;
85 private Reader reader;
86 private OutputStream outStream;
87 private UpdateListener updateListener;
88
89 // As events into ActiveX can't have a return value and can't throw
90 // an exception back to us we handle it here as a callback
91 private Latch updateReturnLatch;
92 private String updateReturnQos;
93 private XmlBlasterException updateReturnException;
94 private long responseWaitTime;
95
96 /**
97 * Create a new access bean.
98 * We read a xmlBlaster.properties file if one is found
99 */
100 public XmlScriptAccess() {
101 this.glob = new Global(); // Reads xmlBlaster.properties
102
103
104 // Wait max 10 minutes for update() method in C#/VB to return:
105 this.responseWaitTime = this.glob.getProperty().get("client/activex/responseWaitTime", 1000L * 60L * 10L);
106 if (log.isLoggable(Level.FINER)) log.finer("Calling ctor of XmlScriptAccess, responseWaitTime=" + this.responseWaitTime);
107
108 // Use socket protocol as default setting
109 String protocol = this.glob.getProperty().get("protocol", "");
110 if ("".equals(protocol)) {
111 try {
112 this.glob.getProperty().set("protocol", "SOCKET");
113 }
114 catch (XmlBlasterException e) {
115 log.severe("Failed setting SOCKET protocol, we continue nevertheless: " + e.toString());
116 }
117 }
118 }
119
120 /**
121 * Add a C# / VisualBasic listener over the ActiveX bridge.
122 * This method is called automatically when activating the bridge
123 */
124 public void addUpdateListener(UpdateListener updateListener) /* throws java.util.TooManyListenersException */ {
125 log.info("Registering update listener");
126 if (log.isLoggable(Level.FINEST)) Thread.dumpStack();
127 this.updateListener = updateListener;
128 }
129
130 /**
131 * Remove a C# / VisualBasic listener.
132 * This method is called automatically when deactivating the bridge
133 */
134 public void removeUpdateListener(UpdateListener updateListener) {
135 log.info("Removing update listener");
136 if (log.isLoggable(Level.FINEST)) Thread.dumpStack();
137 this.updateListener = null;
138 }
139
140 /**
141 * Fire an event into C# / VisualBasic containing an updated message.
142 * <br />
143 * Note: The ActiveX event can't convey a return value or an exception back
144 * to us. There for we block the thread and wait until the
145 * activeX component has delivered us a return value or an exception by
146 * calling setUpdateReturn() or setUpdateException()
147 */
148 protected synchronized String notifyUpdateEvent(String cbSessionId, UpdateKey key, byte[] content, UpdateQos qos) throws XmlBlasterException {
149 if (this.updateListener == null) {
150 log.warning("No updateListener is registered, ignoring " + key.toXml());
151 return "<qos><state id='WARNING'/></qos>";
152 }
153 UpdateEvent ev = new UpdateEvent(this, cbSessionId, key, content, qos);
154 if (log.isLoggable(Level.FINE)) log.fine("Notifying updateListener with new message " + key.toXml());
155 this.updateReturnLatch = new Latch();
156
157 this.updateListener.update(ev);
158
159 boolean awaikened = false;
160 while (true) {
161 try {
162 if (log.isLoggable(Level.FINE)) log.fine("notifyUpdateEvent() Entering wait ...");
163 awaikened = this.updateReturnLatch.attempt(this.responseWaitTime); // block max. milliseconds
164 break;
165 }
166 catch (InterruptedException e) {
167 log.warning("Waking up (waited on " + key.getOid() + " update response): " + e.toString());
168 // try again
169 }
170 }
171 try {
172 if (awaikened) {
173 if (this.updateReturnQos != null) {
174 if (log.isLoggable(Level.FINE)) log.fine("Notifying updateListener done: returned '" + this.updateReturnQos + "'");
175 return this.updateReturnQos;
176 }
177 else if (this.updateReturnException != null) {
178 log.warning("Update failed: " + this.updateReturnException.getMessage());
179 throw this.updateReturnException;
180 }
181 else {
182 log.severe("Update failed, no return available");
183 throw new XmlBlasterException(this.glob, ErrorCode.USER_UPDATE_ERROR, ME, "Update to ActiveX failed, no return available");
184 }
185 }
186 else {
187 String str = "Timeout of " + this.responseWaitTime + " milliseconds occured when waiting on " + key.getOid() + " return value";
188 log.warning(str);
189 throw new XmlBlasterException(glob, ErrorCode.USER_UPDATE_ERROR, ME, str);
190 }
191 }
192 finally {
193 this.updateReturnLatch = null;
194 }
195 }
196
197 /**
198 * ActiveX code needs to call this method to set the return value
199 * for the current update message.
200 * Alternatively you can call setUpdateException() to pass back
201 * an exception.
202 * <br />
203 * Note: You have to call setUpdateReturn() OR setUpdateException()
204 * for each update message to release the blocking thread!
205 *
206 * @param updateReturnQos for example "<qos><state id='OK'/></qos>"
207 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.update.html">The interface.update requirement</a>
208 */
209 public void setUpdateReturn(String updateReturnQos) {
210 if (this.updateReturnLatch == null) {
211 log.warning("Ignoring setUpdateReturn(), updateReturnLatch == null, probably a timeout occurred");
212 return;
213 }
214 this.updateReturnQos = updateReturnQos;
215 this.updateReturnException = null;
216 this.updateReturnLatch.release();
217 if (log.isLoggable(Level.FINER)) log.finer("setUpdateReturn() called");
218 }
219
220 /**
221 * ActiveX code can call this method to return an exception for
222 * the current update message
223 * @param errorCode Only known ErrorCode strings of type "user.*" are allowed
224 * @see org.xmlBlaster.util.def.ErrorCode
225 */
226 public void setUpdateException(String errorCode, String message) {
227 if (this.updateReturnLatch == null) {
228 log.warning("Ignoring setUpdateException(), updateReturnLatch == null, probably a timeout occurred");
229 return;
230 }
231 this.updateReturnQos = null;
232 ErrorCode code = null;
233 try {
234 code = ErrorCode.toErrorCode(errorCode);
235 }
236 catch (IllegalArgumentException e) {
237 log.warning("Don't know error code '" + errorCode + "', changing it to " + ErrorCode.USER_UPDATE_ERROR.toString());
238 message += ": original errorCode=" + errorCode;
239 code = ErrorCode.USER_UPDATE_ERROR;
240 }
241
242 this.updateReturnException = new XmlBlasterException(this.glob, code, ME, message);
243 this.updateReturnLatch.release();
244 if (log.isLoggable(Level.FINER)) log.finer("setUpdateException() called");
245 }
246
247 /**
248 * Access a Properties object to be used later for initialize().
249 * @return We create a new instance for you
250 */
251 public Properties createPropertiesInstance() {
252 return new Properties();
253 }
254
255 /**
256 * Initialize the environment.
257 */
258 public void initialize(Properties properties) {
259 this.glob.init(properties);
260 }
261
262 /**
263 * Initialize the environment.
264 * If you use the initialize(Properties) variant or this method makes no difference.
265 * @param args Command line arguments for example { "-protocol", SOCKET, "-logging", "FINE" }
266 */
267 public void initArgs(String[] args) {
268 this.glob.init(args);
269 }
270
271 /**
272 * Access the handle of this xmlBlaster connection.
273 */
274 public Global getGlobal() {
275 return this.glob;
276 }
277
278 /**
279 * Send xml encoded requests to the xmlBlaster server.
280 * @exception All caught exceptions are thrown as RuntimeException
281 * @see <a href="http://www.xmlblaster.org/xmlBlaster/doc/requirements/client.script.html">client.script requirement</a>
282 */
283 public String sendRequest(String xmlRequest) {
284 try {
285 this.reader = new StringReader(xmlRequest);
286 this.outStream = new ByteArrayOutputStream();
287 // TODO: Dispatch events:
288 this.interpreter = new XmlScriptClient(this.glob, this.glob.getXmlBlasterAccess(),
289 this, null, this.outStream);
290 this.interpreter.parse(this.reader);
291 return this.outStream.toString();
292 }
293 catch (XmlBlasterException e) {
294 log.warning("sendRequest failed: " + e.getMessage());
295 throw new RuntimeException(e.getMessage());
296 }
297 catch (Exception e) {
298 log.severe("sendRequest failed: " + e.toString());
299 e.printStackTrace();
300 throw new RuntimeException(e.toString());
301 }
302 }
303
304 /**
305 * Setup the cache mode.
306 * @see org.xmlBlaster.client.I_XmlBlasterAccess#createSynchronousCache(int)
307 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/client.cache.html">client.cache requirement</a>
308 */
309 public SynchronousCache createSynchronousCache(int size) {
310 return this.glob.getXmlBlasterAccess().createSynchronousCache(size);
311 }
312
313 /**
314 * Login to xmlBlaster.
315 * @see org.xmlBlaster.client.I_XmlBlasterAccess#connect(ConnectQos, org.xmlBlaster.client.I_Callback)
316 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.connect.html">interface.connect requirement</a>
317 */
318 public ConnectReturnQos connect(String xmlQos) throws XmlBlasterException {
319 ConnectQos qos = new ConnectQos(this.glob, this.glob.getConnectQosFactory().readObject(xmlQos));
320 return this.glob.getXmlBlasterAccess().connect(qos, this);
321 }
322
323 /**
324 * Leaves the connection to the server.
325 * The server side resources are not freed if the client has connected fail save
326 * and messages are queued until we login again with the same name and publicSessionId
327 * @see org.xmlBlaster.client.I_XmlBlasterAccess#leaveServer(java.util.Map)
328 */
329 public void leaveServer() {
330 this.glob.getXmlBlasterAccess().leaveServer(null);
331 }
332
333 /**
334 * Has the connect() method successfully passed?
335 * @see org.xmlBlaster.client.I_XmlBlasterAccess#isConnected()
336 */
337 public boolean isConnected() {
338 return this.glob.getXmlBlasterAccess().isConnected();
339 }
340
341 /**
342 * If no communication takes place longer the the lifetime of the session
343 * we can refresh the session to avoid auto logout
344 * @see org.xmlBlaster.client.I_XmlBlasterAccess#refreshSession()
345 */
346 public void refreshSession() throws XmlBlasterException {
347 this.glob.getXmlBlasterAccess().refreshSession();
348 }
349
350 /**
351 * Access the callback server which is currently used in I_XmlBlasterAccess.
352 * @see org.xmlBlaster.client.I_XmlBlasterAccess#getCbServer()
353 */
354 public I_CallbackServer getCbServer() {
355 return this.glob.getXmlBlasterAccess().getCbServer();
356 }
357
358 /**
359 * @see org.xmlBlaster.client.I_XmlBlasterAccess#getId()
360 */
361 public String getId() {
362 return this.glob.getXmlBlasterAccess().getId();
363 }
364
365 /**
366 * Logout from the server, free all server and client side resources.
367 * @return false if connect() wasn't called before or if you call disconnect() multiple times
368 * @see org.xmlBlaster.client.I_XmlBlasterAccess#disconnect(DisconnectQos)
369 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.disconnect.html">interface.disconnect requirement</a>
370 */
371 public boolean disconnect(String xmlQos) throws XmlBlasterException {
372 DisconnectQos disconnectQos = new DisconnectQos(this.glob, glob.getDisconnectQosFactory().readObject(xmlQos));
373 return this.glob.getXmlBlasterAccess().disconnect(disconnectQos);
374 }
375
376 /**
377 * Subscribe to messages.
378 * @param xmlKey Which message topics to retrieve
379 * @param xmlQos Control the behavior and further filter messages with mime based filter plugins
380 * @see org.xmlBlaster.client.I_XmlBlasterAccess#subscribe(SubscribeKey, SubscribeQos, I_Callback)
381 * @exception XmlBlasterException like ErrorCode.USER_NOT_CONNECTED and others
382 */
383 public SubscribeReturnQos subscribe(String xmlKey, String xmlQos) throws XmlBlasterException {
384 return this.glob.getXmlBlasterAccess().subscribe(xmlKey, xmlQos, null);
385 }
386
387 /**
388 * Cancel subscription.
389 * @see org.xmlBlaster.client.I_XmlBlasterAccess#unSubscribe(UnSubscribeKey, UnSubscribeQos)
390 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.unSubscribe.html">interface.unSubscribe requirement</a>
391 */
392 public UnSubscribeReturnQos[] unSubscribe(String xmlKey, String xmlQos) throws XmlBlasterException {
393 return this.glob.getXmlBlasterAccess().unSubscribe(
394 new UnSubscribeKey(this.glob, this.glob.getQueryKeyFactory().readObject(xmlKey)),
395 new UnSubscribeQos(this.glob, this.glob.getQueryQosFactory().readObject(xmlQos)));
396 }
397
398 /**
399 * Get synchronous messages.
400 * @param xmlKey Which message topics to retrieve
401 * @param xmlQos Control the behavior and further filter messages with mime based filter plugins
402 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.get.html">interface.get requirement</a>
403 * @see org.xmlBlaster.client.I_XmlBlasterAccess#get(GetKey, GetQos)
404 * @exception XmlBlasterException like ErrorCode.USER_NOT_CONNECTED and others
405 */
406 public MsgUnit[] get(String xmlKey, String xmlQos) throws XmlBlasterException {
407 return this.glob.getXmlBlasterAccess().get(
408 new GetKey(this.glob, glob.getQueryKeyFactory().readObject(xmlKey)),
409 new GetQos(this.glob, glob.getQueryQosFactory().readObject(xmlQos)));
410 }
411
412 /**
413 * @see org.xmlBlaster.client.I_XmlBlasterAccess#getCached(GetKey, GetQos)
414 */
415 public MsgUnit[] getCached(String xmlKey, String xmlQos) throws XmlBlasterException {
416 return this.glob.getXmlBlasterAccess().getCached(
417 new GetKey(this.glob, glob.getQueryKeyFactory().readObject(xmlKey)),
418 new GetQos(this.glob, glob.getQueryQosFactory().readObject(xmlQos)));
419 }
420
421 //public PublishReturnQos publish(MsgUnit msgUnit) throws XmlBlasterException {
422 // return this.glob.getXmlBlasterAccess().publish(msgUnit);
423 //}
424
425 /**
426 * Publish a message.
427 * @param xmlKey The message topic
428 * @param contentStr The payload as a string
429 * @param xmlQos Control the behavior
430 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.publish.html">interface.publish requirement</a>
431 * @see org.xmlBlaster.client.I_XmlBlasterAccess#publish(org.xmlBlaster.util.MsgUnit)
432 * @exception XmlBlasterException like ErrorCode.USER_NOT_CONNECTED and others
433 */
434 public PublishReturnQos publishStr(String xmlKey, String contentStr, String xmlQos) throws XmlBlasterException {
435 MsgUnit msgUnit = new MsgUnit(this.glob, xmlKey, contentStr, xmlQos);
436 return this.glob.getXmlBlasterAccess().publish(msgUnit);
437 }
438
439 /**
440 * Publish a message.
441 * @param xmlKey The message topic
442 * @param content The payload as binary blob
443 * @param xmlQos Control the behavior
444 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.publish.html">interface.publish requirement</a>
445 * @see org.xmlBlaster.client.I_XmlBlasterAccess#publish(org.xmlBlaster.util.MsgUnit)
446 * @exception XmlBlasterException like ErrorCode.USER_NOT_CONNECTED and others
447 */
448 public PublishReturnQos publishBlob(String xmlKey, byte[] content, String xmlQos) throws XmlBlasterException {
449 MsgUnit msgUnit = new MsgUnit(this.glob, xmlKey, content, xmlQos);
450 return this.glob.getXmlBlasterAccess().publish(msgUnit);
451 }
452
453 /**
454 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.erase.html">interface.erase requirement</a>
455 */
456 public EraseReturnQos[] erase(String xmlKey, String xmlQos) throws XmlBlasterException {
457 return this.glob.getXmlBlasterAccess().erase(
458 new EraseKey(this.glob, glob.getQueryKeyFactory().readObject(xmlKey)),
459 new EraseQos(this.glob, glob.getQueryQosFactory().readObject(xmlQos)));
460 }
461
462 /**
463 * Enforced by I_Callback
464 */
465 public String update(String cbSessionId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) throws XmlBlasterException {
466 if (log.isLoggable(Level.FINER)) log.finer("Callback update arrived: " + updateKey.getOid());
467 return notifyUpdateEvent(cbSessionId, updateKey, content, updateQos);
468 }
469
470 /**
471 * Dump state of this client connection handler into an XML ASCII string.
472 * @return internal state
473 */
474 public String toXml() {
475 return this.glob.getXmlBlasterAccess().toXml();
476 }
477
478 /**
479 * For testing: java org.xmlBlaster.client.activex.XmlScriptAccess
480 */
481 public static void main(String args[]) {
482 try {
483 final XmlScriptAccess access = new XmlScriptAccess();
484 //access.activateUpdateConsumer(true);
485 Properties props = access.createPropertiesInstance();
486 props.put("protocol", "SOCKET");
487 //props.put("trace", "true");
488 access.initialize(props);
489 class TestUpdateListener implements UpdateListener {
490 public void update(UpdateEvent updateEvent) {
491 System.out.println("TestUpdateListener.update: " + updateEvent.getKey());
492 access.setUpdateReturn("<qos><state id='OK'/></qos>");
493 }
494 }
495 TestUpdateListener listener = new TestUpdateListener();
496 access.addUpdateListener(listener);
497 String request = "<xmlBlaster>" +
498 " <connect/>" +
499 " <subscribe><key oid='test'></key><qos/></subscribe>" +
500 " <publish>" +
501 " <key oid='test'><airport name='london'/></key>" +
502 " <content>This is a simple script test</content>" +
503 " <qos/>" +
504 " </publish>" +
505 " <wait delay='1000'/>" +
506 "</xmlBlaster>";
507 String response = access.sendRequest(request);
508 System.out.println("Response is: " + response);
509 //UpdateMsgUnit[] msgs = access.consumeUdateMessages();
510 //for(int i=0; i<msgs.length; i++) {
511 // System.out.println("Access queued update message '" + msgs[i].getUpdateKey().toXml() + "'");
512 //}
513 //msgs = access.consumeUdateMessages();
514 //if (msgs.length != 0)
515 // System.out.println("ERROR: queued update message not consumed properly");
516
517 System.out.println("***** Publishing ...");
518 PublishReturnQos ret = access.publishStr("<key oid='test'/>", "Bla", "<qos/>");
519 System.out.println("***** Published message ret=" + ret.getState());
520 Thread.sleep(2000);
521 response = access.sendRequest("<xmlBlaster>disconnect/></xmlBlaster>");
522 }
523 catch (Throwable e) {
524 System.out.println("ERROR: Caught exception: " + e.toString());
525 }
526 }
527 }
syntax highlighted by Code2HTML, v. 0.9.1