1 /*------------------------------------------------------------------------------
   2 Name:      XmlScriptInterpreter.java
   3 Project:   xmlBlaster.org
   4 Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
   5 ------------------------------------------------------------------------------*/
   6 package org.xmlBlaster.client.script;
   7 
   8 import java.io.File;
   9 import java.io.FileOutputStream;
  10 import java.io.FileReader;
  11 import java.io.IOException;
  12 import java.io.OutputStream;
  13 import java.io.Reader;
  14 import java.io.UnsupportedEncodingException;
  15 import java.util.ArrayList;
  16 import java.util.HashMap;
  17 import java.util.HashSet;
  18 import java.util.Properties;
  19 import java.util.Set;
  20 import java.util.StringTokenizer;
  21 import java.util.TreeSet;
  22 import java.util.logging.Level;
  23 import java.util.logging.Logger;
  24 
  25 import org.xml.sax.Attributes;
  26 import org.xml.sax.InputSource;
  27 import org.xmlBlaster.client.key.UpdateKey;
  28 import org.xmlBlaster.client.qos.UpdateQos;
  29 import org.xmlBlaster.util.EncodableData;
  30 import org.xmlBlaster.util.FileLocator;
  31 import org.xmlBlaster.util.Global;
  32 import org.xmlBlaster.util.I_ReplaceVariable;
  33 import org.xmlBlaster.util.MsgUnit;
  34 import org.xmlBlaster.util.MsgUnitRaw;
  35 import org.xmlBlaster.util.ReplaceVariable;
  36 import org.xmlBlaster.util.SaxHandlerBase;
  37 import org.xmlBlaster.util.StopParseException;
  38 import org.xmlBlaster.util.XmlBlasterException;
  39 import org.xmlBlaster.util.XmlBuffer;
  40 import org.xmlBlaster.util.def.Constants;
  41 import org.xmlBlaster.util.def.ErrorCode;
  42 import org.xmlBlaster.util.def.MethodName;
  43 import org.xmlBlaster.util.xbformat.MsgInfo;
  44 import org.xmlBlaster.util.xbformat.XmlScriptParser;
  45 
  46 
  47 /**
  48  * Parse email body. 
  49  *
  50  *  <subscribe>
  51  *     <key>....</key>
  52  *     <qos>...</qos>
  53  *  </subscribe>
  54  *
  55  *  <unSubscribe>
  56  *     <key>
  57  *     </key>
  58  *  </unSubscribe>
  59  *
  60  *  <publish>
  61  *     <key></key>
  62  *     <content link='ff'></content>
  63  *     <qos>...</qos>  
  64  *  </publish>
  65  *
  66  *  <publishArr>
  67  *     <key></key>
  68  *     <content link='ff'></content>
  69  *     <qos>...</qos>
  70  *     <key></key>
  71  *     <content link='ff'></content>
  72  *     <qos>...</qos>
  73  *       .......
  74  *     <key></key>
  75  *     <content link='ff'></content>
  76  *     <qos>...</qos>
  77  *  </publishArr>
  78  *
  79  *  <erase>
  80  *     <key></key>
  81  *     <qos>...</qos>
  82  *  </erase>
  83  *
  84  *  <erase>
  85  *     <key></key>
  86  *     <qos>...</qos>
  87  *  </erase>
  88  */
  89 
  90 
  91 /**
  92  * Abstract class to parse and construct a XML represantation of xmlBlaster invocations for scripting. 
  93  * <p>
  94  * Example for command line scripting usage:
  95  * </p>
  96  * <p>
  97  * <tt>
  98  * java javaclients.XmlScript -requestFile inFile.xml -responseFile outFile.xml -updateFile updFile.xml
  99  * </tt>
 100  * </p>
 101  * @author <a href="mailto:michele@laghi.eu">Michele Laghi</a>
 102  * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/client.script.html">The client.script requirement</a>
 103  */
 104 public abstract class XmlScriptInterpreter extends SaxHandlerBase {
 105    
 106    private final String ME = "XmlScriptInterpreter";
 107    private static Logger log = Logger.getLogger(XmlScriptInterpreter.class.getName());
 108    protected Global glob;
 109    
 110    /** a set of names of allowed commands */   
 111    private HashSet commandsToFire = new HashSet();
 112    
 113    protected StringBuffer qos = new StringBuffer();
 114    protected StringBuffer key = new StringBuffer();
 115    private StringBuffer content = new StringBuffer(); // To access use contentData
 116    protected StringBuffer cdata = new StringBuffer();
 117 
 118    /** Replace e.g. ${ICAO} with command line setting '-ICAO EDDI' */
 119    private boolean replaceQosTokens;
 120    private boolean replaceKeyTokens;
 121    private boolean replaceContentTokens;
 122    private boolean replaceFileContentTokens;
 123    /** Replace tokens in wait or echo markup */
 124    private boolean replaceTokens;
 125 
 126    /** Encapsulates the content of the current message (useful for encoding) */
 127    protected EncodableData contentData;
 128    // private boolean inQos, inKey, inContent;
 129    private int inQos, inKey, inContent, inCDATA;
 130    private String link;
 131    private String contentUrl; // file:/tmp/demo.png
 132    private String sessionId;
 133    private String requestId;
 134    private byte type; // I=invoke R=response E=exception
 135    private String  propertyName;
 136    private boolean inProperty;
 137 
 138    /** the attachments (some contents can be in the attachments) */
 139    private HashMap attachments;
 140    
 141    /** used to accumulate all messages to be sent with publishArr */
 142    protected ArrayList messageList;
 143    
 144    /** buffer used as a place holder for the responses of the xmlBlaster invocations */
 145    protected StringBuffer response;
 146    protected boolean needsRootEndTag;
 147    protected OutputStream out;
 148    
 149    protected Object waitMutex = new Object();
 150    protected long updateCounter;
 151    protected int waitNumUpdates;
 152    
 153    /**
 154     * Set true to send a simple exception format like
 155       <pre>
 156       &lt;update type='E'>
 157        &lt;qos>&lt;state id='ERROR' info='user'/>&lt;/qos>
 158       &lt;/update>
 159       </pre>
 160       <p>Note: The errorCode is stripped to the main category</p>
 161     */
 162    protected boolean sendSimpleExceptionFormat;
 163    protected String simpleExceptionFormatList;
 164    
 165    protected boolean forceReadable;
 166    protected boolean inhibitContentCDATAWrapping;
 167    
 168    public final static String ROOT_TAG = "xmlBlaster";
 169    public final static String ROOTRESPONSE_TAG = "xmlBlasterResponse";
 170    public final String KEY_TAG = MsgUnitRaw.KEY_TAG;
 171    public final String CONTENT_TAG = MsgUnitRaw.CONTENT_TAG;
 172    public final String QOS_TAG = MsgUnitRaw.QOS_TAG;
 173 
 174    public final String ECHO_TAG = "echo";
 175    public final String INPUT_TAG = "input";
 176    public final String WAIT_TAG = "wait";
 177    
 178    
 179    /**
 180     * You need to call initialize() if using this default constructor. 
 181     */
 182    public XmlScriptInterpreter() {
 183    }
 184    
 185    /**
 186     * This constructor is the most generic one (more degrees of freedom)
 187     * @param glob the global to use
 188     * @param attachments the attachments where to search
 189     *           when a content is stored in the attachment
 190     *           (with the 'link' attribute); can be null
 191     * @param out the OutputStream where to send the responses of the invocations done to xmlBlaster
 192     */
 193    public XmlScriptInterpreter(Global glob, HashMap attachments, OutputStream out) {
 194       super(glob);
 195       initialize(glob, attachments, out);
 196    }
 197 
 198    /**
 199     * @param glob the global to use
 200     * @param attachments the attachments where to search when a content is stored in the attachment (with the 'link' attribute)
 201     * @param out the OutputStream where to send the responses of the invocations done to xmlBlaster
 202     */
 203    public void initialize(Global glob, HashMap attachments, OutputStream out) {
 204       this.glob = glob;
 205 
 206       setUseLexicalHandler(true);
 207       this.commandsToFire.add(MethodName.GET.toString());
 208       this.commandsToFire.add(MethodName.CONNECT.toString());
 209       this.commandsToFire.add(MethodName.PING.toString());
 210       this.commandsToFire.add(MethodName.SUBSCRIBE.toString());
 211       this.commandsToFire.add(MethodName.UNSUBSCRIBE.toString());
 212       this.commandsToFire.add(MethodName.PUBLISH.toString());
 213       this.commandsToFire.add(MethodName.PUBLISH_ARR.toString());
 214       this.commandsToFire.add(MethodName.PUBLISH_ONEWAY.toString());
 215       this.commandsToFire.add(MethodName.UPDATE.toString());
 216       this.commandsToFire.add(MethodName.UPDATE_ONEWAY.toString());
 217       this.commandsToFire.add(MethodName.ERASE.toString());
 218       this.commandsToFire.add(MethodName.DISCONNECT.toString());
 219       this.commandsToFire.add(MethodName.EXCEPTION.toString());
 220 
 221       this.attachments = attachments;
 222       this.out = out;
 223       this.needsRootEndTag = false; // will be true when start tag is written
 224    }
 225 
 226    /**
 227     * converts the tag sctart to a string
 228     * @param qName
 229     * @param attr
 230     * @return
 231     */
 232    protected String writeElementStart(String qName, Attributes attr) {
 233       StringBuffer buf = new StringBuffer("<");
 234       buf.append(qName);
 235       int nmax = attr.getLength();
 236       for (int i=0; i < nmax; i++) {
 237          String name = attr.getQName(i);
 238          String value = attr.getValue(i);
 239          buf.append(' ');
 240          buf.append(name);
 241          buf.append("='");
 242          buf.append(value);
 243          buf.append('\'');
 244       }
 245       buf.append('>');
 246       return buf.toString();
 247    }
 248 
 249    /**
 250     * Parses the given reader and executes the specified commands.
 251     * @param in the reader from which to read the xml input.
 252     * @throws XmlBlasterException
 253     */
 254    public synchronized void parse(Reader in) throws XmlBlasterException {
 255       this.inQos = 0;
 256       this.inKey = 0;
 257       this.inContent = 0;
 258       this.inCDATA = 0;
 259       if (this.out != null) {
 260          try {
 261             this.out.flush();
 262          }
 263          catch (IOException ex) {
 264             throw new XmlBlasterException(this.glob, ErrorCode.INTERNAL_UNKNOWN, ME + ".parse: could not flush the output stream: original message: '" + ex.toString() + "'");
 265          }
 266       }
 267       super.init(new InputSource(in)); // parse with SAX
 268    }
 269 
 270    public void characters(char[] ch, int start, int length) {
 271       // append on the corresponding buffer
 272       if (this.inCDATA > 0) {
 273          this.cdata.append(ch, start, length);
 274       }
 275       else if (this.inQos > 0) {
 276        String tmp = new String(ch, start, length);
 277        tmp = XmlBuffer.escape(tmp);
 278          this.qos.append(tmp);
 279       }
 280       else if (this.inKey > 0) {
 281        String tmp = new String(ch, start, length);
 282        tmp = XmlBuffer.escape(tmp);
 283          this.key.append(tmp);
 284       }
 285       else if (this.inContent > 0) {
 286          this.content.append(ch, start, length);
 287       }
 288       else super.characters(ch, start, length);
 289    }
 290 
 291    /**
 292     * Increments the corresponding counter only if it is already in one such element
 293     * @param qName
 294     */
 295    private void incrementInElementCounters(String qName) {
 296       if (QOS_TAG.equals(qName) && this.inQos > 0) this.inQos++;
 297       else if (KEY_TAG.equals(qName) && this.inKey > 0) this.inKey++;
 298       else if (CONTENT_TAG.equals(qName) && this.inContent > 0) this.inContent++;
 299    }
 300 
 301    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
 302       checkNestedTags();
 303       if (this.inQos > 0) {
 304          this.qos.append(writeElementStart(qName, atts));
 305          incrementInElementCounters(qName);
 306          return;
 307       }
 308       
 309       if (this.inKey > 0) {
 310          this.key.append(writeElementStart(qName, atts));
 311          incrementInElementCounters(qName);
 312          return;
 313       }
 314 
 315       if (this.inContent > 0) {
 316          this.content.append(writeElementStart(qName, atts));
 317          incrementInElementCounters(qName);
 318          return;
 319       }
 320 
 321       if (this.commandsToFire.contains(qName)) {
 322          this.character = new StringBuffer();
 323          this.character.append(this.writeElementStart(qName, atts));
 324          if (MethodName.PUBLISH_ARR.equals(qName) ||
 325              MethodName.PUBLISH_ONEWAY.equals(qName))
 326             this.messageList = new ArrayList();
 327          this.sessionId = atts.getValue("sessionId");
 328          this.requestId = atts.getValue("requestId");
 329          String typeStr = atts.getValue("type");
 330          if (typeStr!=null && typeStr.length() > 0)
 331             this.type = typeStr.getBytes()[0];
 332          else
 333             this.type = MsgInfo.INVOKE_BYTE; // 'I'
 334          return;
 335       }
 336 
 337       if ("replaceTokens".equals(qName)) {
 338           this.replaceTokens = true;
 339           return;
 340        }
 341        
 342       if ("replaceKeyTokens".equals(qName)) {
 343          this.replaceKeyTokens = true;
 344          return;
 345       }
 346       
 347       if (KEY_TAG.equals(qName)) {
 348          this.inKey++;
 349          this.key.setLength(0);
 350          this.key.append(this.writeElementStart(qName, atts));
 351          return;
 352       }
 353 
 354       if ("replaceQosTokens".equals(qName)) {
 355          this.replaceQosTokens = true;
 356          return;
 357       }
 358 
 359       if (QOS_TAG.equals(qName)) {
 360          this.inQos++;
 361          this.qos.setLength(0);
 362          this.qos.append(this.writeElementStart(qName, atts));
 363          return;
 364       }
 365 
 366       if ("replaceContentTokens".equals(qName)) {
 367          this.replaceContentTokens = true;
 368          return;
 369       }
 370 
 371       if ("replaceFileContentTokens".equals(qName)) {
 372           this.replaceFileContentTokens = true;
 373           return;
 374        }
 375 
 376       if (CONTENT_TAG.equals(qName)) {
 377          this.inContent++;
 378          this.link = null;
 379          this.content.setLength(0);
 380          String sizeStr = atts.getValue("size"); // long
 381          String type = atts.getValue("type"); // byte[]
 382          String encoding = atts.getValue("encoding"); // base64
 383          this.contentUrl = atts.getValue("url"); // file:/tmp/demo.png
 384          if (this.contentUrl != null && replaceContentTokens) {
 385             this.contentUrl = replaceVariable(this.contentUrl);
 386          }
 387          this.contentData = new EncodableData(CONTENT_TAG, null, type, encoding);
 388          try {
 389             if (sizeStr != null) {
 390                long size = Long.valueOf(sizeStr).longValue();
 391                if (size > 0)
 392                   this.contentData.setSize(size);
 393             }
 394          } catch (NumberFormatException e) {}
 395          String tmp = atts.getValue("link");
 396          if (tmp != null && tmp.length() > 0) this.link = tmp;
 397          // this.content.append(this.writeElementStart(qName, atts));
 398          return;
 399       }
 400       
 401       if (WAIT_TAG.equals(qName)) {
 402          String message = atts.getValue("message");
 403          if (message != null) {
 404                 message = replaceVariables(message);
 405             System.out.println(message);
 406          }
 407          this.waitNumUpdates = 0;
 408          String tmp = atts.getValue("updates");
 409          tmp = replaceVariables(tmp);
 410          if (tmp != null) {
 411             try {
 412                this.waitNumUpdates = Integer.parseInt(tmp.trim());
 413             }
 414             catch (Throwable e) {
 415             }
 416          }
 417          long delay = Integer.MAX_VALUE;
 418          tmp = atts.getValue("delay");
 419          tmp = replaceVariables(tmp);
 420          if (tmp != null) {
 421             try {
 422                delay = Long.parseLong(tmp.trim());
 423                if (delay == 0) delay = Integer.MAX_VALUE;
 424             }
 425             catch (Throwable e) {
 426             }
 427          }
 428          if (this.waitNumUpdates > 0 || delay > 0) {
 429            synchronized (this.waitMutex) {
 430               if (this.waitNumUpdates > 0 && this.updateCounter >= this.waitNumUpdates) {
 431                  // updates have arrived already
 432               }
 433               else {
 434                  try {
 435                   waitMutex.wait(delay);
 436                  }
 437                  catch (InterruptedException e) {
 438                  }
 439                  if (this.waitNumUpdates == 0 || this.updateCounter < this.waitNumUpdates) {
 440                     log.info("wait timeout occurred after " + delay + " milli.");
 441                  }
 442                  this.updateCounter = 0;
 443               }
 444            }
 445          }
 446          return;
 447       }
 448 
 449       if (ECHO_TAG.equals(qName)) { // console output <echo message="Hello"/>
 450          String message = atts.getValue("message");
 451          if (message == null) {
 452             message = "";
 453          }
 454          message = replaceVariables(message);
 455          System.out.println(message);
 456          return;
 457       }
 458 
 459       if (INPUT_TAG.equals(qName)) { // User input from console <input message="Hit a key: " delay="100"/>
 460          String inputMessage = atts.getValue("message");
 461          if (inputMessage == null) {
 462             inputMessage = "Hit a key to continue> ";
 463          }
 464          inputMessage = replaceVariables(inputMessage);
 465          // this.validargs = atts.getValue("validargs"); "y/n"
 466          {  // Wait a bit to have updates processed
 467             String tmp = atts.getValue("delay");
 468             if (tmp == null) {
 469                tmp = "500";
 470             }
 471             tmp = replaceVariables(tmp);
 472            try {
 473               long delay = Long.parseLong(tmp.trim());
 474               Thread.sleep(delay);
 475            }
 476            catch (Throwable e) {
 477            }
 478          }
 479          /*int ret = */Global.waitOnKeyboardHit(inputMessage);
 480          return;
 481       }
 482 
 483       if (ROOT_TAG.equals(qName)) { // "xmlBlaster"
 484          String id = atts.getValue("id");
 485          this.response = new StringBuffer("<").append(ROOTRESPONSE_TAG);
 486          if (id != null) this.response.append(" id='").append(id).append("'");         
 487          this.response.append(">\n");
 488          this.needsRootEndTag = true;
 489          return;
 490       }
 491 
 492       if ("property".equals(qName)) {
 493          this.inProperty = true;
 494          // <property name='transactionId'>Something&lt;/property>
 495          this.propertyName = atts.getValue("name");
 496          return;
 497       }
 498    }
 499    
 500    private String replaceVariables(String template) {
 501       if (!replaceTokens) return template;
 502       
 503       ReplaceVariable r = new ReplaceVariable();
 504       String result = r.replace(template,
 505          new I_ReplaceVariable() {
 506             public String get(String key) {
 507                return glob.getProperty().get(key, key);
 508             }
 509          });
 510       return result;
 511    }
 512 
 513    /**
 514     * Appends the end stream to the current StringBuffer
 515     * @param buf the StringBuffer to be used 
 516     * @param qName the name of the command
 517     */
 518    private void appendEndOfElement(StringBuffer buf, String qName) {
 519       buf.append("</");
 520       buf.append(qName);
 521       buf.append('>');
 522    }
 523 
 524    /**
 525     * Replace e.g. ${XY} with the variable from global properties. 
 526     * @param text The complete string which may contain zero to many ${...}
 527     *             variables, if null we return null
 528     * @return The new value where all found ${} are replaced.
 529     */
 530    public String replaceVariable(String text) {
 531       if (text == null) return null;
 532       int lastFrom = -1;
 533       for (int i=0; i<20; i++) { // max recursion/replacement depth
 534          int from = text.indexOf("${");
 535          if (from == -1) return text;
 536          if (lastFrom != -1 && lastFrom == from) return text; // recursion
 537          int to = text.indexOf("}", from);
 538          if (to == -1) return text;
 539          String key = text.substring(from+2, to);
 540          String value = glob.getProperty().get(key, "${"+key+"}");
 541          text = text.substring(0,from) + value + text.substring(to+1);
 542          lastFrom = from;
 543       }
 544       return text;
 545    }
 546 
 547    /**
 548     * On each remote method invocation this function is called. 
 549     * @param methodName
 550     * @param type 'I'=invoke 'R'=response 'E'=exception
 551     * @return true: The methodName was known and is successfully processed
 552     *         false: The methodName is not known and nothing is processed
 553     * @throws XmlBlasterException Will lead to stop parsing further
 554     */
 555    abstract public boolean fireMethod(MethodName methodName,
 556          String sessionId, String requestId, byte type) throws XmlBlasterException;
 557 
 558    /**
 559     * Set a property into Global scope. 
 560     */
 561    abstract public void setProperty(String key, String value) throws XmlBlasterException;
 562 
 563    /**
 564     * Fires the given xmlBlaster command and sends the response to the output stream
 565     * @param qName
 566     */
 567    private void fireCommand(String qName) throws XmlBlasterException {
 568       if (replaceQosTokens) {
 569          String tmp = this.qos.toString();
 570          this.qos.setLength(0);
 571          this.qos.append(replaceVariable(tmp));
 572       }
 573       if (replaceKeyTokens) {
 574          String tmp = this.key.toString();
 575          this.key.setLength(0);
 576          this.key.append(replaceVariable(tmp));
 577       }
 578       if (replaceContentTokens) {
 579          String tmp = this.content.toString();
 580          this.content.setLength(0);
 581          this.content.append(replaceVariable(tmp));
 582       }
 583       
 584       if (QOS_TAG.equals(qName)) {
 585          this.inQos--;
 586          return;
 587       }
 588       if (KEY_TAG.equals(qName)) {
 589          this.inKey--;
 590          return;
 591       }
 592       if (CONTENT_TAG.equals(qName)) {
 593          this.inContent--;
 594          return;
 595       }
 596 
 597       boolean processed = fireMethod(MethodName.toMethodName(qName), this.sessionId, this.requestId, this.type);
 598       if (processed) {
 599          if (this.content != null) this.content.setLength(0);
 600          if (this.qos != null) this.qos.setLength(0);
 601          if (this.key != null) this.key.setLength(0);
 602          return;
 603       }
 604    }
 605 
 606    protected void flushResponse() throws XmlBlasterException {
 607       try {
 608          if (this.out != null) {
 609             if (log.isLoggable(Level.FINE)) log.fine("Sending response: " + this.response.toString());
 610             synchronized(this.out) {
 611                this.out.write(this.response.toString().getBytes("UTF-8"));
 612             }
 613          }   
 614       }
 615       catch (IOException ex) {
 616          log.severe("flushResponse exception occured " + ex.getMessage());
 617          throw XmlBlasterException.convert(this.glob, ME, ErrorCode.INTERNAL_UNKNOWN.toString(), ex);
 618       }
 619       finally {
 620          this.response = new StringBuffer();
 621       }
 622    }
 623 
 624    protected MsgUnit buildMsgUnit() throws XmlBlasterException {
 625       byte[] currentContent = null;
 626       if (this.contentUrl != null) {
 627         if (this.contentUrl.startsWith("file://")) // TODO: Support all URLS
 628            this.contentUrl = this.contentUrl.substring("file://".length());
 629         currentContent = FileLocator.readFile(this.contentUrl);
 630       }
 631       else if (this.link != null) {
 632          if (this.attachments != null && this.attachments.containsKey(this.link)) {
 633             Object obj = this.attachments.get(this.link);
 634             if (obj instanceof String) {
 635                if (this.contentData == null)
 636                   this.contentData = new EncodableData(CONTENT_TAG, null, Constants.TYPE_STRING, Constants.ENCODING_NONE);
 637                this.contentData.setValueRaw((String)obj);
 638                currentContent = this.contentData.getBlobValue();
 639             }
 640             else {
 641                currentContent = (byte[])obj;
 642             }
 643          }
 644          else {
 645             throw new XmlBlasterException(this.glob, ErrorCode.USER_ILLEGALARGUMENT, ME, "buildMsgUnit: the attachment '" + this.link + "' was not found");
 646             // throw exception
 647          }
 648       }
 649       else {
 650         currentContent = (this.contentData == null) ? new byte[0] : this.contentData.getBlobValue();
 651       }
 652       
 653       if (this.replaceFileContentTokens) {
 654       try {
 655         String currentContentStr = new String(currentContent, "UTF-8");
 656         ReplaceVariable rv = new ReplaceVariable();
 657         currentContentStr = rv.replace(currentContentStr, new I_ReplaceVariable() {
 658                // @Override
 659          public String get(String key) {
 660             try {
 661                String value = glob.get(key, key, null, null);
 662                return value;
 663             } catch (XmlBlasterException e) {
 664                e.printStackTrace();
 665                return key;
 666             }
 667             //return System.getProperty(key);
 668          }
 669         });
 670         currentContent = currentContentStr.getBytes("UTF-8");
 671       } catch (UnsupportedEncodingException e) {
 672          e.printStackTrace();
 673       }
 674       }
 675 
 676       MsgUnit msgUnit = new MsgUnit(this.key.toString(),
 677          currentContent, this.qos.toString());
 678       return msgUnit;
 679    }
 680 
 681    private void checkNestedTags() {
 682       int sum = 0;
 683       if (this.inContent > 0 ) sum++;
 684       if (this.inKey > 0) sum++;
 685       if (this.inQos > 0) sum++;
 686       /*
 687       if (sum > 1) {
 688          Thread.dumpStack();
 689          log.error(ME, "check: there is an internal error!! Mismatch with the nested tags ...");
 690       } 
 691       */
 692    }
 693 
 694    public void endElement(String namespaceURI, String localName, String qName) {
 695       try {
 696          checkNestedTags();
 697          if (this.inQos > 0) {
 698             appendEndOfElement(this.qos, qName);
 699             if (QOS_TAG.equals(qName) && this.inQos > 0) this.inQos--;
 700             return;
 701          }
 702          if (this.inKey > 0) {
 703             appendEndOfElement(this.key, qName);
 704             if (KEY_TAG.equals(qName) && this.inKey > 0) this.inKey--;
 705             return;
 706          }
 707          if (CONTENT_TAG.equals(qName)) {
 708             if (this.inContent > 0) this.inContent--;
 709             if (this.inContent > 0) appendEndOfElement(this.content, qName); // because nested content tags should be there (only the outher not)
 710             this.contentData.setValueRaw(this.content.toString());
 711             return;
 712          }
 713          if (ROOT_TAG.equals(qName)) { // "xmlBlaster"
 714             this.response = new StringBuffer("\n</");
 715             this.response.append(ROOTRESPONSE_TAG).append(">\n");
 716             flushResponse();
 717             this.needsRootEndTag = false;
 718          }  
 719          if ("message".equals(qName)) {
 720             try {
 721                if (this.messageList == null) {
 722                   throw new XmlBlasterException(glob, ErrorCode.USER_ILLEGALARGUMENT, ME, "To send multiple messages please use " + MethodName.PUBLISH_ARR + " or " + MethodName.PUBLISH_ONEWAY);
 723                }
 724                this.messageList.add(buildMsgUnit());
 725             }
 726             catch(XmlBlasterException e) {
 727                log.severe("endElement '" + qName + "' exception occurred when trying to build message unit: " + e.getMessage());         }
 728             return;
 729          }
 730          // comes here since the end tag is not part of the content
 731          if (this.inContent > 0) appendEndOfElement(this.content, qName);
 732 
 733          if ("property".equals(qName) && this.inProperty) {
 734             this.inProperty = false;
 735             if (this.propertyName != null) {
 736                setProperty(this.propertyName, character.toString().trim());
 737             }
 738             character.setLength(0);
 739             return;
 740          }
 741 
 742          if (this.commandsToFire.contains(qName)) {
 743             appendEndOfElement(this.character, qName);
 744             fireCommand(qName);
 745             return;
 746          }
 747       }
 748       catch (XmlBlasterException e) {
 749          if (log.isLoggable(Level.FINE)) XmlScriptInterpreter.log.fine("endElement exception occured " + e.getMessage());
 750          if (this.needsRootEndTag) { // is </xmlBlasterResponse> missing?
 751             this.response = new StringBuffer("\n</");
 752             this.response.append(ROOTRESPONSE_TAG).append(">\n");
 753             try {
 754                flushResponse();
 755             } catch (XmlBlasterException e1) {
 756                e1.printStackTrace();
 757             }
 758          }
 759          throw new StopParseException(e);
 760       }
 761       catch (StopParseException e) {
 762          if (this.needsRootEndTag) { // is </xmlBlasterResponse> missing?
 763             this.response = new StringBuffer("\n</");
 764             this.response.append(ROOTRESPONSE_TAG).append(">\n");
 765             try {
 766                flushResponse();
 767             } catch (XmlBlasterException e1) {
 768                e1.printStackTrace();
 769             }
 770          }
 771          throw e;
 772       }
 773    }
 774 
 775    public void startCDATA() {
 776       if (log.isLoggable(Level.FINER)) XmlScriptInterpreter.log.finer("startCDATA");
 777       this.inCDATA++;
 778       if (this.inContent == 0)
 779          this.cdata.append("<![CDATA[");
 780    }
 781    
 782    public void endCDATA() {
 783       if (log.isLoggable(Level.FINER)) {
 784          String txt = "";
 785          if (this.qos != null) this.qos.toString();
 786          log.finer("endCDATA: " + txt);
 787       }
 788       this.inCDATA--;
 789       if (this.inContent == 0) 
 790          this.cdata.append("]]>");
 791       if (this.inCDATA == 0) {
 792          // append on the corresponding buffer
 793          if (this.inQos > 0) this.qos.append(this.cdata);
 794          else if (this.inKey > 0) this.key.append(this.cdata);
 795          else if (this.inContent > 0) this.content.append(this.cdata);
 796          else super.character.append(this.cdata);
 797          this.cdata = new StringBuffer();
 798       }
 799    }
 800 
 801    public String wrapForScripting(MsgUnit msgUnit, String comment) {
 802       return wrapForScripting(ROOT_TAG, msgUnit, comment);
 803    }
 804 
 805    /**
 806     * Dump the MsgUnit to XML, the dump includes the xml header (UTF-8). 
 807     * <br />
 808     * Example: 
 809     * <pre>
 810     * String xml = XmlScriptInterpreter.wrapForScripting(
 811     *      XmlScriptInterpreter.ROOT_TAG,
 812     *       msgUnit,
 813     *      "XmlScripting dump");
 814     *</pre>
 815     * @param rootTag Usually XmlScriptInterpreter.ROOT_TAG="xmlBlaster"
 816     * @param msgUnit null is OK
 817     * @param comment Some comment you want to add or null
 818     * @return
 819     */
 820    public static String wrapForScripting(String rootTag, MsgUnit msgUnit, String comment) {
 821       MsgUnit[] msgUnitArr = (msgUnit == null) ? new MsgUnit[0] : new MsgUnit[1];
 822       if (msgUnitArr.length == 1) msgUnitArr[0] = msgUnit;
 823       return wrapForScripting(rootTag, msgUnitArr, comment);
 824    }
 825 
 826    public static String wrapForScripting(String rootTag, MsgUnit[] msgUnitArr, String comment) {
 827       if (msgUnitArr == null) msgUnitArr = new MsgUnit[0];
 828       StringBuffer sb = new StringBuffer(1024+(msgUnitArr.length*256));
 829       sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
 830       sb.append("\n<").append(rootTag).append(">");
 831       if (comment != null && comment.length() > 0) sb.append("\n<!-- " + comment + " -->");
 832       for (int i=0; i<msgUnitArr.length; i++) {
 833          MsgUnit msgUnit = msgUnitArr[i];
 834          String xml = msgUnit.toXml((String)null, (Properties)null);
 835          sb.append("\n <").append(msgUnit.getMethodName().toString()).append(">");
 836          sb.append(xml);
 837          sb.append("\n </").append(msgUnit.getMethodName().toString()).append(">");
 838       }
 839       sb.append("\n</").append(rootTag).append(">");
 840       return sb.toString();
 841    }
 842    
 843    /**
 844     * Dump the given MsgUnitRaw to XML.
 845     * Example for a PublishReturnQos:
 846     * <pre>
 847     * &lt;-- A comment -->
 848     *   &lt;publish>
 849      &lt;qos>
 850       &lt;key oid='1'/>
 851       &lt;rcvTimestamp nanos='1131654994574000000'/>
 852      &lt;isPublish/>
 853      &lt;/qos>
 854   &lt;/publish>
 855 </pre>
 856     * @param methodName The method to invoke, like "publishArr"
 857     * @param sessionId An optional sessionId or null
 858     * @param requestId An optional requestId or null
 859     * @param msgUnits The msgUnits to serialize
 860     * @param header For example 
 861 <?xml version='1.0' encoding='UTF-8'?>
 862     * @param comment An optional comment to add, can be null
 863     * @param schemaDecl Used for root tag, for example
 864 xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' 
 865 xsi:noNamespaceSchemaLocation='xmlBlasterPublish.xsd'
 866     * @param out The output sink for the result
 867     * @param type 'I' is for invoke, 'R' for reply and 'E' for exception
 868     * @throws XmlBlasterException
 869     */
 870    protected void serialize(MethodName methodName, String sessionId,
 871          String requestId, MsgUnitRaw[] msgUnits,
 872          String header, String comment, String schemaDecl,
 873          OutputStream out, byte type) throws IOException {
 874       String offset = " ";
 875       StringBuffer sb = new StringBuffer(1024+(1024));
 876       
 877       if (header == null) header = "<?xml version='1.0' encoding='UTF-8'?>\n";
 878       sb.append(header);
 879       
 880       if (comment != null && comment.length() > 0)
 881          sb.append("\n<!-- ").append(comment).append( " -->");
 882       if (methodName != null) {
 883          sb.append("\n<").append(methodName.toString());
 884          if (schemaDecl != null && schemaDecl.length() > 0)
 885             sb.append(" ").append(schemaDecl);
 886          if (sessionId != null && sessionId.length() > 0)
 887             sb.append(" sessionId='").append(sessionId).append("'");
 888          if (requestId != null && requestId.length() > 0)
 889             sb.append(" requestId='").append(requestId).append("'");
 890          if (type != 0) {
 891             sb.append(" type='").append(MsgInfo.getTypeChar(type)).append("'");
 892          }
 893          sb.append(">");
 894       }
 895       out.write(sb.toString().getBytes());
 896       
 897       if (msgUnits != null && msgUnits.length > 0) {
 898          Properties props = null;
 899          if (forceReadable) {
 900             props = new Properties();
 901             props.setProperty(Constants.TOXML_FORCEREADABLE, "true");
 902             if (inhibitContentCDATAWrapping)
 903                props.setProperty("inhibitContentCDATAWrapping", "true");
 904          }
 905             
 906          // String contentCharset = ReplaceVariable.extractWithMatchingAttrs(msgUnits[0].getQos(), "clientProperty", Constants.CLIENTPROPERTY_CONTENT_CHARSET);
 907          // String contentCharset = XmlScriptParser.getEncodingFromProcessingInstruction(header);
 908          String contentCharset = null;
 909          if (msgUnits.length == 1) {
 910             if (type == MsgInfo.EXCEPTION_BYTE && this.sendSimpleExceptionFormat) {
 911                // This must be parsable on the other side similar to XmlBlasterException.parseByteArr()
 912                // See code in MsgInfo.java
 913                //ErrorCode errorCode = ErrorCode.getCategory(msgUnits[0].getQos()); -> toplevel only, like 'user' 
 914                ErrorCode errorCode = ErrorCode.toErrorCode(msgUnits[0].getQos()); 
 915                StringBuffer buf = new StringBuffer(1024);
 916                buf.append("<qos><state id='ERROR' info='").append(simplifiedErrorCode(simpleExceptionFormatList, errorCode.getErrorCode()));
 917                buf.append("'/></qos>");
 918                out.write(buf.toString().getBytes(Constants.UTF8_ENCODING));
 919             }
 920             else {
 921                msgUnits[0].toXml(offset, out, props, contentCharset);
 922             }
 923          }
 924          else {
 925             for (int i=0; i < msgUnits.length; i++) {
 926                out.write("\n  <message>".getBytes());
 927                msgUnits[i].toXml(offset, out, props, contentCharset);
 928                out.write("\n  </message>\n".getBytes());
 929             }
 930          }
 931       }
 932       
 933       if (methodName != null) {
 934          out.write("\n</".getBytes());
 935          out.write(methodName.toString().getBytes());
 936          out.write(">\n".getBytes());
 937       }
 938       out.flush();
 939    }
 940    
 941    /**
 942     * made public only for testing purposes
 943     * @param errCode
 944     * @return
 945     */
 946    public static String simplifiedErrorCode(String codeList, String errCode) {
 947       if (errCode == null)
 948          return null;
 949       if (codeList == null || codeList.trim().length() < 1)
 950          return errCode;
 951       Set<String> set = getSimpleExceptionFormatList(codeList);
 952       if (set.contains(errCode.trim()))
 953          return errCode;
 954       
 955       String simpleErrCode = "internal"; // the default since we do not know better
 956       String[] keys = set.toArray(new String[set.size()]);
 957       boolean found = false;
 958       for (int i=0; i < keys.length; i++) {
 959          if (errCode.startsWith(keys[i])) {
 960             simpleErrCode = keys[i];
 961             found = true;
 962          }
 963          else if (found)
 964             break;
 965       }
 966       log.info("simplifying '" + errCode + "' to '" + simpleErrCode + "'");
 967       return simpleErrCode;
 968    }
 969 
 970    private static Set<String> getSimpleExceptionFormatList(String list) {
 971       StringTokenizer tokenizer = new StringTokenizer(list.trim(), ":");
 972       Set<String> allowedCodes = new TreeSet<String>();
 973       while (tokenizer.hasMoreTokens()){
 974          allowedCodes.add(tokenizer.nextToken().trim());
 975       }
 976       
 977       return allowedCodes;
 978    }
 979    
 980    
 981    public static String dumpToFile(String path, String fn, String xml) throws XmlBlasterException {
 982       try {
 983          if (path != null) {
 984             File dir = new File(path);
 985             dir.mkdirs();
 986          }
 987          File to_file = new File(path, fn);
 988          FileOutputStream to = new FileOutputStream(to_file);
 989          to.write(xml.getBytes());
 990          to.close();
 991          return to_file.toString();
 992       }
 993       catch (java.io.IOException e) {
 994          throw new XmlBlasterException(Global.instance(), ErrorCode.USER_ILLEGALARGUMENT, "dumpToFile", "Please check your '"+path+"' or file name '" + fn + "'", e);
 995       }
 996    }
 997  
 998    /**
 999     * If a callback handler was registered, we will be notified here
1000     * about updates as well
1001     * @param cbSessionId
1002     * @param updateKey
1003     * @param content
1004     * @param updateQos
1005     * @return
1006     * @throws XmlBlasterException
1007     */
1008    public String update(String cbSessionId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) throws XmlBlasterException {
1009       synchronized (this.waitMutex) {
1010          if (updateQos.isOk())
1011             this.updateCounter++;
1012          if (this.waitNumUpdates > 0 && this.updateCounter >= this.waitNumUpdates) {
1013                 if (this.updateCounter == this.waitNumUpdates) log.info("Fire notify, " + this.updateCounter + " updates arrived");
1014                 waitMutex.notify();
1015          }
1016       }
1017       if (log.isLoggable(Level.FINE)) log.fine("Received #" + this.updateCounter);
1018                 return null;
1019         }
1020    
1021    public static void main(String[] args) {
1022       try {
1023          Global glob = new Global();
1024          if (args.length == 0) {
1025             System.out.println("Usage: java org.xmlBlaster.client.script.XmlScriptInterpreter in.xml");
1026             System.exit(1);
1027          }
1028          String[] tmp = (args.length > 1) ? new String[args.length-2] : new String[args.length-1];
1029          for (int i=0; i < tmp.length; i++) tmp[i] = args[i+2];
1030          
1031          glob.init(tmp);
1032          //OutputStream out = (args.length > 1) ? new FileOutputStream(args[1]) : System.out;
1033          OutputStream out = (args.length > 1) ? new FileOutputStream(args[1]) : new FileOutputStream("out.xml");
1034          XmlScriptInterpreter interpreter = new XmlScriptClient(glob, out);
1035          FileReader in = new FileReader(args[0]);
1036          interpreter.parse(in);
1037       }
1038       catch (Exception ex) {
1039          ex.printStackTrace();
1040       }
1041    }
1042    
1043    
1044 }


syntax highlighted by Code2HTML, v. 0.9.1