001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015: package org.griphyn.vdl.invocation;
016:
017: import org.griphyn.common.util.Currently;
018: import java.util.*;
019: import java.net.InetAddress;
020: import java.io.Writer;
021: import java.io.IOException;
022:
023: /**
024: * This class is the container for an invocation record. The record
025: * itself contains information about the job or jobs that ran, the total
026: * usage, and information about central files that were used.
027: *
028: * @author Jens-S. Vöckler
029: * @author Yong Zhao
030: * @version $Revision: 118 $
031: */
032: public class InvocationRecord extends Invocation // implements Cloneable
033: {
034: /**
035: * The "official" namespace URI of the invocation record schema.
036: */
037: public static final String SCHEMA_NAMESPACE = "http://pegasus.isi.edu/schema/invocation";
038:
039: /**
040: * The "not-so-official" location URL of the invocation record definition.
041: */
042: public static final String SCHEMA_LOCATION = "http://pegasus.isi.edu/schema/iv-2.0.xsd";
043:
044: /**
045: * protocol version information.
046: */
047: private String m_version;
048:
049: /**
050: * start of gridlaunch timestamp.
051: */
052: private Date m_start;
053:
054: /**
055: * total duration of call.
056: */
057: private double m_duration;
058:
059: /**
060: * Name of the: Transformation that produced this invocation.
061: */
062: private String m_transformation;
063:
064: /**
065: * Name of the Derivation that produced this invocation.
066: */
067: private String m_derivation;
068:
069: /**
070: * process id of gridlaunch itself.
071: */
072: private int m_pid;
073:
074: /**
075: * host address where gridlaunch ran (primary interface).
076: */
077: private InetAddress m_hostaddr;
078:
079: /**
080: * Symbolic hostname where gridlaunch ran (primary interface).
081: */
082: private String m_hostname;
083:
084: /**
085: * numerical user id of the effective user.
086: */
087: private int m_uid;
088:
089: /**
090: * symbolical user name of the effective user.
091: */
092: private String m_user;
093:
094: /**
095: * numerical group id of the effective user.
096: */
097: private int m_gid;
098:
099: /**
100: * symbolical group name of the effective user.
101: */
102: private String m_group;
103:
104: /**
105: * Working directory at startup.
106: */
107: private WorkingDir m_cwd;
108:
109: /**
110: * Architectural information.
111: */
112: private Architecture m_uname;
113:
114: /**
115: * Total resource consumption by gridlaunch and all siblings.
116: */
117: private Usage m_usage;
118:
119: /**
120: * Job records: prejob, main job, postjob
121: */
122: private List m_job;
123:
124: /**
125: * Array with stat() and fstat() information about various files.
126: */
127: private List m_stat;
128:
129: /**
130: * Resource, site or pool at which the jobs was run.
131: */
132: private String m_resource;
133:
134: /**
135: * Workflow label, currently optional?
136: */
137: private String m_wf_label;
138:
139: /**
140: * Workflow timestamp to make the label more unique.
141: */
142: private Date m_wf_stamp;
143:
144: /**
145: * Environment settings.
146: */
147: private Environment m_environment;
148:
149: /**
150: * Currently active umask while kickstart was executing. This
151: * is available with new kickstart, older version will have -1
152: * at this API point.
153: */
154: private int m_umask = -1;
155:
156: /**
157: * Accessor
158: *
159: * @see #setUMask(int)
160: */
161: public int getUMask() {
162: return this .m_umask;
163: }
164:
165: /**
166: * Accessor.
167: *
168: * @param m_umask
169: * @see #getUMask()
170: */
171: public void setUMask(int umask) {
172: this .m_umask = umask;
173: }
174:
175: /**
176: * Default c'tor: Construct a hollow shell and allow further
177: * information to be added later.
178: */
179: public InvocationRecord() {
180: m_stat = new ArrayList(5);
181: m_job = new ArrayList(3);
182: m_environment = null;
183: }
184:
185: /**
186: * Accessor
187: *
188: * @see #setVersion(String)
189: */
190: public String getVersion() {
191: return this .m_version;
192: }
193:
194: /**
195: * Accessor.
196: *
197: * @param version
198: * @see #getVersion()
199: */
200: public void setVersion(String version) {
201: this .m_version = version;
202: }
203:
204: /**
205: * Accessor
206: *
207: * @see #setStart(Date)
208: */
209: public Date getStart() {
210: return this .m_start;
211: }
212:
213: /**
214: * Accessor.
215: *
216: * @param start
217: * @see #getStart()
218: */
219: public void setStart(Date start) {
220: this .m_start = start;
221: }
222:
223: /**
224: * Accessor
225: *
226: * @see #setDuration(double)
227: */
228: public double getDuration() {
229: return this .m_duration;
230: }
231:
232: /**
233: * Accessor.
234: *
235: * @param duration
236: * @see #getDuration()
237: */
238: public void setDuration(double duration) {
239: this .m_duration = duration;
240: }
241:
242: /**
243: * Accessor
244: *
245: * @see #setTransformation(String)
246: */
247: public String getTransformation() {
248: return this .m_transformation;
249: }
250:
251: /**
252: * Accessor.
253: *
254: * @param transformation
255: * @see #getTransformation()
256: */
257: public void setTransformation(String transformation) {
258: this .m_transformation = transformation;
259: }
260:
261: /**
262: * Accessor
263: *
264: * @see #setDerivation(String)
265: */
266: public String getDerivation() {
267: return this .m_derivation;
268: }
269:
270: /**
271: * Accessor.
272: *
273: * @param derivation
274: * @see #getDerivation()
275: */
276: public void setDerivation(String derivation) {
277: this .m_derivation = derivation;
278: }
279:
280: /**
281: * Accessor
282: *
283: * @see #setPID(int)
284: */
285: public int getPID() {
286: return this .m_pid;
287: }
288:
289: /**
290: * Accessor.
291: *
292: * @param pid
293: * @see #getPID()
294: */
295: public void setPID(int pid) {
296: this .m_pid = pid;
297: }
298:
299: /**
300: * Accessor
301: *
302: * @see #setHostAddress(InetAddress)
303: */
304: public InetAddress getHostAddress() {
305: return this .m_hostaddr;
306: }
307:
308: /**
309: * Accessor.
310: *
311: * @param hostaddr
312: * @see #getHostAddress()
313: */
314: public void setHostAddress(InetAddress hostaddr) {
315: this .m_hostaddr = hostaddr;
316: }
317:
318: /**
319: * Accessor
320: *
321: * @see #setHostname(String)
322: */
323: public String getHostname() {
324: return this .m_hostname;
325: }
326:
327: /**
328: * Accessor.
329: *
330: * @param hostname
331: * @see #getHostname()
332: */
333: public void setHostname(String hostname) {
334: this .m_hostname = hostname;
335: }
336:
337: /**
338: * Accessor
339: *
340: * @see #setUID(int)
341: */
342: public int getUID() {
343: return this .m_uid;
344: }
345:
346: /**
347: * Accessor.
348: *
349: * @param uid
350: * @see #getUID()
351: */
352: public void setUID(int uid) {
353: this .m_uid = uid;
354: }
355:
356: /**
357: * Accessor
358: *
359: * @see #setUser(String)
360: */
361: public String getUser() {
362: return this .m_user;
363: }
364:
365: /**
366: * Accessor.
367: *
368: * @param user
369: * @see #getUser()
370: */
371: public void setUser(String user) {
372: this .m_user = user;
373: }
374:
375: /**
376: * Accessor
377: *
378: * @see #setGID(int)
379: */
380: public int getGID() {
381: return this .m_gid;
382: }
383:
384: /**
385: * Accessor.
386: *
387: * @param gid
388: * @see #getGID()
389: */
390: public void setGID(int gid) {
391: this .m_gid = gid;
392: }
393:
394: /**
395: * Accessor
396: *
397: * @see #setGroup(String)
398: */
399: public String getGroup() {
400: return this .m_group;
401: }
402:
403: /**
404: * Accessor.
405: *
406: * @param group
407: * @see #getGroup()
408: */
409: public void setGroup(String group) {
410: this .m_group = group;
411: }
412:
413: /**
414: * Accessor
415: *
416: * @see #setUsage(Usage)
417: */
418: public Usage getUsage() {
419: return this .m_usage;
420: }
421:
422: /**
423: * Accessor.
424: *
425: * @param usage
426: * @see #getUsage()
427: */
428: public void setUsage(Usage usage) {
429: this .m_usage = usage;
430: }
431:
432: /**
433: * Accessor
434: *
435: * @see #setArchitecture(Architecture)
436: */
437: public Architecture getArchitecture() {
438: return this .m_uname;
439: }
440:
441: /**
442: * Accessor.
443: *
444: * @param uname
445: * @see #getArchitecture()
446: */
447: public void setArchitecture(Architecture uname) {
448: this .m_uname = uname;
449: }
450:
451: /**
452: * Accessor
453: *
454: * @see #setResource( String )
455: */
456: public String getResource() {
457: return this .m_resource;
458: }
459:
460: /**
461: * Accessor.
462: *
463: * @param resource
464: * @see #getResource()
465: */
466: public void setResource(String resource) {
467: this .m_resource = resource;
468: }
469:
470: /**
471: * Accessor
472: *
473: * @see #setWorkflowLabel( String )
474: */
475: public String getWorkflowLabel() {
476: return this .m_wf_label;
477: }
478:
479: /**
480: * Accessor.
481: *
482: * @param label
483: * @see #getWorkflowLabel()
484: */
485: public void setWorkflowLabel(String label) {
486: this .m_wf_label = label;
487: }
488:
489: /**
490: * Accessor
491: *
492: * @see #setWorkflowTimestamp( Date )
493: */
494: public Date getWorkflowTimestamp() {
495: return this .m_wf_stamp;
496: }
497:
498: /**
499: * Accessor.
500: *
501: * @param stamp
502: * @see #getResource()
503: */
504: public void setWorkflowTimestamp(Date stamp) {
505: this .m_wf_stamp = stamp;
506: }
507:
508: /**
509: * Accessor
510: *
511: * @see #setEnvironment(Environment)
512: */
513: public Environment getEnvironment() {
514: return this .m_environment;
515: }
516:
517: /**
518: * Accessor.
519: *
520: * @param environment
521: * @see #getEnvironment()
522: */
523: public void setEnvironment(Environment environment) {
524: this .m_environment = environment;
525: }
526:
527: // /**
528: // * Parses an ISO 8601 timestamp?
529: // *
530: // * @param stamp
531: // * @see #getResource()
532: // */
533: // public void setWorkflowTimestamp( String stamp )
534: // { this.m_wf_stamp = stamp; }
535:
536: /**
537: * Accessor: Appends a job to the list of jobs.
538: *
539: * @param job is the job to append to the list.
540: */
541: public void addJob(Job job) {
542: this .m_job.add(job);
543: }
544:
545: /**
546: * Accessor: Inserts a Job into a specific position of the job list.
547: *
548: * @param index is the position to insert the item into
549: * @param job is the job to insert into the list.
550: */
551: public void addJob(int index, Job job) {
552: this .m_job.add(index, job);
553: }
554:
555: /**
556: * Accessor: Obtains a job at a certain position in the job list.
557: *
558: * @param index is the position in the list to obtain a job from
559: * @return the job at that position.
560: * @throws IndexOutOfBoundsException if the index points to an element
561: * in the list that does not contain any elments.
562: */
563: public Job getJob(int index) throws IndexOutOfBoundsException {
564: //-- check bound for index
565: if ((index < 0) || (index >= this .m_job.size()))
566: throw new IndexOutOfBoundsException();
567:
568: return (Job) this .m_job.get(index);
569: }
570:
571: /**
572: * Accessor: Obtains the size of the job list.
573: *
574: * @return number of elements that an external array needs to be sized to.
575: */
576: public int getJobCount() {
577: return this .m_job.size();
578: }
579:
580: /**
581: * Accessor: Gets an array of all values that constitute the current
582: * content. This list is read-only.
583: *
584: * @return a list of jobs.
585: */
586: public java.util.List getJobList() {
587: return Collections.unmodifiableList(this .m_job);
588: }
589:
590: /**
591: * Accessor: Enumerates the internal values that constitute the content
592: * of the job list.
593: *
594: * @return an iterator to walk the list with.
595: */
596: public Iterator iterateJob() {
597: return this .m_job.iterator();
598: }
599:
600: /**
601: * Accessor: Enumerates the internal values that constitute the content
602: * of the job list.
603: *
604: * @return a list iterator to walk the list with.
605: */
606: public ListIterator listIterateJob() {
607: return this .m_job.listIterator();
608: }
609:
610: /**
611: * Accessor: Removes all values from the job list.
612: */
613: public void removeAllJob() {
614: this .m_job.clear();
615: }
616:
617: /**
618: * Accessor: Removes a specific job from the job list.
619: * @param index is the position at which an element is to be removed.
620: * @return the job that was removed.
621: */
622: public Job removeJob(int index) {
623: return (Job) this .m_job.remove(index);
624: }
625:
626: /**
627: * Accessor: Overwrites a job at a certain position.
628: *
629: * @param index position to overwrite an elment in.
630: * @param job is the Job to replace with.
631: * @throws IndexOutOfBoundsException if the position pointed to is invalid.
632: */
633: public void setJob(int index, Job job)
634: throws IndexOutOfBoundsException {
635: //-- check bounds for index
636: if ((index < 0) || (index >= this .m_job.size())) {
637: throw new IndexOutOfBoundsException();
638: }
639: this .m_job.set(index, job);
640: }
641:
642: /**
643: * Accessor: Overwrites internal list with an external list
644: * representing jobs.
645: *
646: * @param jobs is the external list of job to overwrite with.
647: */
648: public void setJob(Collection jobs) {
649: this .m_job.clear();
650: this .m_job.addAll(jobs);
651: }
652:
653: /**
654: * Accessor: Appends a stat to the list of stats.
655: *
656: * @param stat is the stat to append to the list.
657: */
658: public void addStatCall(StatCall stat) {
659: this .m_stat.add(stat);
660: }
661:
662: /**
663: * Accessor: Inserts a StatCall into a specific position of the stat list.
664: *
665: * @param index is the position to insert the item into
666: * @param stat is the stat to insert into the list.
667: */
668: public void addStatCall(int index, StatCall stat) {
669: this .m_stat.add(index, stat);
670: }
671:
672: /**
673: * Accessor: Obtains a stat at a certain position in the stat list.
674: *
675: * @param index is the position in the list to obtain a stat from
676: * @return the stat at that position.
677: * @throws IndexOutOfBoundsException if the index points to an element
678: * in the list that does not contain any elments.
679: */
680: public StatCall getStatCall(int index)
681: throws IndexOutOfBoundsException {
682: //-- check bound for index
683: if ((index < 0) || (index >= this .m_stat.size()))
684: throw new IndexOutOfBoundsException();
685:
686: return (StatCall) this .m_stat.get(index);
687: }
688:
689: /**
690: * Accessor: Obtains the size of the stat list.
691: *
692: * @return number of elements that an external array needs to be sized to.
693: */
694: public int getStatCount() {
695: return this .m_stat.size();
696: }
697:
698: /**
699: * Accessor: Gets an array of all values that constitute the current
700: * content. This list is read-only.
701: *
702: * @return a list of stats.
703: */
704: public java.util.List getStatList() {
705: return Collections.unmodifiableList(this .m_stat);
706: }
707:
708: /**
709: * Accessor: Enumerates the internal values that constitute the content
710: * of the stat list.
711: *
712: * @return an iterator to walk the list with.
713: */
714: public Iterator iterateStatCall() {
715: return this .m_stat.iterator();
716: }
717:
718: /**
719: * Accessor: Enumerates the internal values that constitute the content
720: * of the stat list.
721: *
722: * @return a list iterator to walk the list with.
723: */
724: public ListIterator listIterateStatCall() {
725: return this .m_stat.listIterator();
726: }
727:
728: /**
729: * Accessor: Removes all values from the stat list.
730: */
731: public void removeAllStatCall() {
732: this .m_stat.clear();
733: }
734:
735: /**
736: * Accessor: Removes a specific stat from the stat list.
737: * @param index is the position at which an element is to be removed.
738: * @return the stat that was removed.
739: */
740: public StatCall removeStatCall(int index) {
741: return (StatCall) this .m_stat.remove(index);
742: }
743:
744: /**
745: * Accessor: Overwrites a stat at a certain position.
746: *
747: * @param index position to overwrite an elment in.
748: * @param stat is the StatCall to replace with.
749: * @throws IndexOutOfBoundsException if the position pointed to is invalid.
750: */
751: public void setStatCall(int index, StatCall stat)
752: throws IndexOutOfBoundsException {
753: //-- check bounds for index
754: if ((index < 0) || (index >= this .m_stat.size())) {
755: throw new IndexOutOfBoundsException();
756: }
757: this .m_stat.set(index, stat);
758: }
759:
760: /**
761: * Accessor: Overwrites internal list with an external list
762: * representing stats.
763: *
764: * @param stats is the external list of stat to overwrite with.
765: */
766: public void setStatCall(Collection stats) {
767: this .m_stat.clear();
768: this .m_stat.addAll(stats);
769: }
770:
771: /**
772: * Accessor
773: *
774: * @see #setWorkingDirectory(WorkingDir)
775: * @see #setWorkingDirectory(String)
776: */
777: public WorkingDir getWorkingDirectory() {
778: return this .m_cwd;
779: }
780:
781: /**
782: * Accessor.
783: *
784: * @param cwd
785: * @see #getWorkingDirectory()
786: * @see #setWorkingDirectory(WorkingDir)
787: */
788: public void setWorkingDirectory(String cwd) {
789: this .m_cwd = new WorkingDir(cwd);
790: }
791:
792: /**
793: * Accessor.
794: *
795: * @param cwd
796: * @see #getWorkingDirectory()
797: * @see #setWorkingDirectory(String)
798: */
799: public void setWorkingDirectory(WorkingDir cwd) {
800: this .m_cwd = cwd;
801: }
802:
803: /**
804: * Converts the active state into something meant for human consumption.
805: * The method will be called when recursively traversing the instance
806: * tree.
807: *
808: * @param stream is a stream opened and ready for writing. This can also
809: * be a string stream for efficient output.
810: */
811: public void toString(Writer stream) throws IOException {
812: throw new IOException(
813: "method not implemented, please contact vds-support@griphyn.org");
814: }
815:
816: /**
817: * Writes the header of the XML output. The output contains the special
818: * strings to start an XML document, some comments, and the root element.
819: * The latter points to the XML schema via XML Instances.
820: *
821: * @param stream is a stream opened and ready for writing. This can also
822: * be a string stream for efficient output.
823: * @param indent is a <code>String</code> of spaces used for pretty
824: * printing. The initial amount of spaces should be an empty string.
825: * The parameter is used internally for the recursive traversal.
826: * @param namespace is the XML schema namespace prefix. If neither
827: * empty nor null, each element will be prefixed with this prefix,
828: * and the root element will map the XML namespace.
829: * @exception IOException if something fishy happens to the stream.
830: */
831: public void writeXMLHeader(Writer stream, String indent,
832: String namespace) throws IOException {
833: String newline = System.getProperty("line.separator", "\r\n");
834:
835: // intro
836: if (indent != null && indent.length() > 0)
837: stream.write(indent);
838: stream.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
839: stream.write(newline);
840:
841: // when was this document generated
842: if (indent != null && indent.length() > 0)
843: stream.write(indent);
844: stream.write("<!-- generated: ");
845: stream.write(Currently.iso8601(false));
846: stream.write(" -->");
847: stream.write(newline);
848:
849: // who generated this document
850: if (indent != null && indent.length() > 0)
851: stream.write(indent);
852: stream.write("<!-- generated by: ");
853: stream.write(System.getProperties().getProperty("user.name",
854: "unknown"));
855: stream.write(" [");
856: stream.write(System.getProperties().getProperty("user.region",
857: "??"));
858: stream.write("] -->");
859: stream.write(newline);
860:
861: // root element with elementary attributes
862: if (indent != null && indent.length() > 0)
863: stream.write(indent);
864: stream.write('<');
865: if (namespace != null && namespace.length() > 0) {
866: stream.write(namespace);
867: stream.write(':');
868: }
869: stream.write("invocation xmlns");
870: if (namespace != null && namespace.length() > 0) {
871: stream.write(':');
872: stream.write(namespace);
873: }
874: stream.write("=\"");
875: stream.write(SCHEMA_NAMESPACE);
876: stream
877: .write("\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"");
878: stream.write(SCHEMA_NAMESPACE);
879: stream.write(' ');
880: stream.write(SCHEMA_LOCATION);
881: stream.write("\"");
882:
883: writeAttribute(stream, " version=\"", this .m_version);
884: writeAttribute(stream, " start=\"", Currently.iso8601(false,
885: true, true, this .m_start));
886: writeAttribute(stream, " duration=\"", Double
887: .toString(this .m_duration));
888: if (this .m_transformation != null
889: && this .m_transformation.length() > 0)
890: writeAttribute(stream, " transformation=\"",
891: this .m_transformation);
892: if (this .m_derivation != null && this .m_derivation.length() > 0)
893: writeAttribute(stream, " derivation=\"", this .m_derivation);
894: writeAttribute(stream, " pid=\"", Integer.toString(this .m_pid));
895: if (this .m_resource != null && this .m_resource.length() > 0)
896: writeAttribute(stream, " resource=\"", this .m_resource);
897: if (this .m_wf_label != null && this .m_wf_label.length() > 0)
898: writeAttribute(stream, " wf-label=\"", this .m_wf_label);
899: if (this .m_wf_stamp != null)
900: writeAttribute(stream, " wf-stamp=\"", Currently.iso8601(
901: false, true, true, this .m_wf_stamp));
902: writeAttribute(stream, " hostaddr=\"", this .m_hostaddr
903: .getHostAddress());
904: if (this .m_hostname != null && this .m_hostname.length() > 0)
905: writeAttribute(stream, " hostname=\"", this .m_hostname);
906: writeAttribute(stream, " uid=\"", Integer.toString(this .m_uid));
907: if (this .m_user != null && this .m_user.length() > 0)
908: writeAttribute(stream, " user=\"", this .m_user);
909: writeAttribute(stream, " gid=\"", Integer.toString(this .m_gid));
910: if (this .m_group != null && this .m_group.length() > 0)
911: writeAttribute(stream, " group=\"", this .m_group);
912:
913: stream.write('>');
914: if (indent != null)
915: stream.write(newline);
916: }
917:
918: /**
919: * Dump the state of the current element as XML output. This function
920: * traverses all sibling classes as necessary, and converts the data
921: * into pretty-printed XML output. The stream interface should be able
922: * to handle large output efficiently.
923: *
924: * @param stream is a stream opened and ready for writing. This can also
925: * be a string stream for efficient output.
926: * @param indent is a <code>String</code> of spaces used for pretty
927: * printing. The initial amount of spaces should be an empty string.
928: * The parameter is used internally for the recursive traversal.
929: * @param namespace is the XML schema namespace prefix. If neither
930: * empty nor null, each element will be prefixed with this prefix,
931: * and the root element will map the XML namespace.
932: * @exception IOException if something fishy happens to the stream.
933: */
934: public void toXML(Writer stream, String indent, String namespace)
935: throws IOException {
936: // write prefix
937: writeXMLHeader(stream, indent, namespace);
938:
939: // part 1: jobs
940: String newindent = indent == null ? null : indent + " ";
941: for (Iterator i = this .m_job.iterator(); i.hasNext();) {
942: ((Job) i.next()).toXML(stream, newindent, namespace);
943: }
944:
945: // part 2: cwd and total usage
946: m_cwd.toXML(stream, newindent, namespace);
947: m_usage.toXML(stream, newindent, namespace);
948: m_uname.toXML(stream, newindent, namespace);
949:
950: // part 3: statcall records
951: for (Iterator i = this .m_stat.iterator(); i.hasNext();) {
952: ((StatCall) i.next()).toXML(stream, newindent, namespace);
953: }
954:
955: // part 4: environment and resourcs
956: if (m_environment != null)
957: m_environment.toXML(stream, newindent, namespace);
958:
959: // close tag
960: if (indent != null && indent.length() > 0)
961: stream.write(indent);
962: stream.write("</");
963: if (namespace != null && namespace.length() > 0) {
964: stream.write(namespace);
965: stream.write(':');
966: }
967: stream.write("invocation>");
968: stream.write(System.getProperty("line.separator", "\r\n"));
969: stream.flush(); // this is the only time we flush
970: }
971: }
|