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 <update type='E'>
157 <qos><state id='ERROR' info='user'/></qos>
158 </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</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 * <-- A comment -->
848 * <publish>
849 <qos>
850 <key oid='1'/>
851 <rcvTimestamp nanos='1131654994574000000'/>
852 <isPublish/>
853 </qos>
854 </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