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