001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.webadapter;
039:
040: import org.millstone.base.Application;
041: import org.millstone.base.terminal.ApplicationResource;
042: import org.millstone.base.terminal.ExternalResource;
043: import org.millstone.base.terminal.PaintException;
044: import org.millstone.base.terminal.Paintable;
045: import org.millstone.base.terminal.Resource;
046: import org.millstone.base.terminal.ThemeResource;
047: import org.millstone.base.terminal.VariableOwner;
048: import org.millstone.base.terminal.UploadStream;
049: import org.millstone.base.terminal.PaintTarget;
050:
051: import java.util.Stack;
052:
053: /** User Interface Description Language Target.
054: * @author IT Mill Ltd.
055: * @version 3.1.1
056: * @since 3.0
057: */
058: public class WebPaintTarget implements PaintTarget {
059:
060: /* Document type declarations */
061: private final static String UIDL_XML_DECL = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
062: private final static String UIDL_DOCTYPE_DECL = "<!DOCTYPE uidl PUBLIC \"-//MILLSTONE//DTD UIDL//EN\" \"http://millstone.org/xml/3.0/UIDL.dtd\">";
063: /* commonly used tags and argument names */
064: private final static String UIDL_TAG_VARIABLE = "var";
065: private final static String UIDL_ARG_NAME = "name";
066: private final static String UIDL_ARG_VALUE = "value";
067: private final static String UIDL_ARG_ID = "id";
068: private final static String UIDL_ARG_TYPE = "type";
069: private Stack mOpenTags;
070: private boolean mTagArgumentListOpen;
071: private StringBuffer uidlBuffer;
072: private StringBuffer tagBuffer;
073: private HttpVariableMap variableMap;
074: private boolean closed = false;
075: private WebAdapterServlet webAdapterServlet;
076: private Theme theme;
077: private static final int TAG_BUFFER_DEFAULT_SIZE = 20;
078: private boolean mSuppressOutput = false;
079:
080: /** Create a new XMLPrintWriter, without automatic line flushing.
081: *
082: *
083: * @param out A character-output stream.
084: */
085: public WebPaintTarget(HttpVariableMap variableMap,
086: UIDLTransformerType type,
087: WebAdapterServlet webAdapterServlet, Theme theme)
088: throws PaintException {
089:
090: // Host servlet
091: this .webAdapterServlet = webAdapterServlet;
092:
093: // Target theme
094: this .theme = theme;
095:
096: // Set the variable map
097: this .variableMap = variableMap;
098:
099: // Set the target for UIDL writing
100: this .uidlBuffer = new StringBuffer();
101:
102: // Set the target for TAG data
103: this .tagBuffer = new StringBuffer();
104:
105: // Initialize tag-writing
106: mOpenTags = new Stack();
107: mTagArgumentListOpen = false;
108:
109: //Add document declaration
110: this .print(UIDL_XML_DECL + "\n\n");
111:
112: // In debug mode add DOCTYPE element
113: if (webAdapterServlet.isDebugMode()) {
114: //this.print(UIDL_DOCTYPE_DECL + "\n\n");
115: }
116: // Add UIDL start tag and its attributes
117: this .startTag("uidl");
118:
119: // Name of the active theme
120: this .addAttribute("theme", type.getTheme().getName());
121:
122: }
123:
124: /** Ensures that the currently open element tag is closed.
125: */
126: private void ensureClosedTag() {
127: if (mTagArgumentListOpen) {
128: tagBuffer.append(">");
129: mTagArgumentListOpen = false;
130: append(tagBuffer);
131: }
132: }
133:
134: /** Print element start tag.
135: *
136: * <pre>Todo:
137: * Checking of input values
138: * </pre>
139: *
140: * @param tagName The name of the start tag
141: *
142: */
143: public void startTag(String tagName) throws PaintException {
144: // In case of null data output nothing:
145: if (tagName == null)
146: throw new NullPointerException();
147:
148: //Ensure that the target is open
149: if (this .closed)
150: throw new PaintException(
151: "Attempted to write to a closed PaintTarget.");
152:
153: // Make sure that the open start tag is closed before
154: // anything is written.
155: ensureClosedTag();
156:
157: // Check tagName and attributes here
158: mOpenTags.push(tagName);
159: tagBuffer = new StringBuffer(TAG_BUFFER_DEFAULT_SIZE);
160:
161: // Print the tag with attributes
162: tagBuffer.append("<" + tagName);
163:
164: mTagArgumentListOpen = true;
165: }
166:
167: /** Print element end tag.
168: *
169: * If the parent tag is closed before
170: * every child tag is closed an MillstoneException is raised.
171: *
172: * @param tag The name of the end tag
173: */
174: public void endTag(String tagName) throws PaintException {
175: // In case of null data output nothing:
176: if (tagName == null)
177: throw new NullPointerException();
178:
179: //Ensure that the target is open
180: if (this .closed)
181: throw new PaintException(
182: "Attempted to write to a closed PaintTarget.");
183:
184: String lastTag = "";
185:
186: lastTag = (String) mOpenTags.pop();
187: if (!tagName.equalsIgnoreCase(lastTag))
188: throw new PaintException(
189: "Invalid UIDL: wrong ending tag: '" + tagName
190: + "' expected: '" + lastTag + "'.");
191:
192: // Make sure that the open start tag is closed before
193: // anything is written.
194: ensureClosedTag();
195:
196: //Write the end (closing) tag
197: append("</" + lastTag + "\n>");
198:
199: // NOTE: We re-enable the output (if it has been disabled)
200: // for subsequent tags. The output is suppressed if tag
201: // contains attribute "invisible" with value true.
202: mSuppressOutput = false;
203: }
204:
205: /** Append data into UIDL output buffer.
206: *
207: * @param data String to be appended.
208: */
209: private void append(String data) {
210: if (!mSuppressOutput) {
211: uidlBuffer.append(data);
212: }
213: }
214:
215: /** Append data into UIDL output buffer.
216: *
217: * @param data StringBuffer to be appended.
218: */
219: private void append(StringBuffer data) {
220: if (!mSuppressOutput) {
221: uidlBuffer.append(data);
222: }
223: }
224:
225: /** Substitute the XML sensitive characters with predefined XML entities.
226: *
227: * @return A new string instance where all occurrences of XML sensitive
228: * characters are substituted with entities.
229: */
230: static public String escapeXML(String xml) {
231: if (xml == null || xml.length() <= 0)
232: return "";
233: return escapeXML(new StringBuffer(xml)).toString();
234: }
235:
236: /** Substitute the XML sensitive characters with predefined XML entities.
237: * @param xml the String to be substituted
238: * @return A new StringBuffer instance where all occurrences of XML
239: * sensitive characters are substituted with entities.
240: *
241: */
242: static public StringBuffer escapeXML(StringBuffer xml) {
243: if (xml == null || xml.length() <= 0)
244: return new StringBuffer("");
245:
246: StringBuffer result = new StringBuffer(xml.length() * 2);
247:
248: for (int i = 0; i < xml.length(); i++) {
249: char c = xml.charAt(i);
250: String s = toXmlChar(c);
251: if (s != null) {
252: result.append(s);
253: } else {
254: result.append(c);
255: }
256: }
257: return result;
258: }
259:
260: /** Substitute a XML sensitive character with predefined XML entity.
261: * @param c Character to be replaced with an entity.
262: * @return String of the entity or null if character is not to be replaced
263: * with an entity.
264: */
265: private static String toXmlChar(char c) {
266: switch (c) {
267: case '&':
268: return "&"; // & => &
269: case '>':
270: return ">"; // > => >
271: case '<':
272: return "<"; // < => <
273: case '"':
274: return """; // " => "
275: case '\'':
276: return "'"; // ' => '
277: default:
278: return null;
279: }
280: }
281:
282: /** Print XML.
283: *
284: * Writes pre-formatted XML to stream. Well-formness of XML is checked.
285: * <pre>
286: * TODO: XML checking should be made
287: * </pre>
288: */
289: private void print(String str) {
290: // In case of null data output nothing:
291: if (str == null)
292: return;
293:
294: // Make sure that the open start tag is closed before
295: // anything is written.
296: ensureClosedTag();
297:
298: // Write what was given
299: append(str);
300: }
301:
302: /** Print XML-escaped text.
303: *
304: */
305: public void addText(String str) throws PaintException {
306: addUIDL(escapeXML(str));
307: }
308:
309: /** Adds a boolean attribute to component.
310: * Atributes must be added before any content is written.
311: *
312: * @param name Attribute name
313: * @param value Attribute value
314: */
315: public void addAttribute(String name, boolean value)
316: throws PaintException {
317: if ("invisible".equals(name) && value) {
318: // NOTE: If we receive the "invisible attribute
319: // we filter these tags (and ceontent) from
320: // them out from the output.
321: this .mSuppressOutput = true;
322: } else {
323: addAttribute(name, String.valueOf(value));
324: }
325: }
326:
327: /** Adds a resource attribute to component.
328: * Atributes must be added before any content is written.
329: *
330: * @param name Attribute name
331: * @param value Attribute value
332: */
333: public void addAttribute(String name, Resource value)
334: throws PaintException {
335:
336: if (value instanceof ExternalResource) {
337: addAttribute(name, ((ExternalResource) value).getURL());
338:
339: } else if (value instanceof ApplicationResource) {
340: ApplicationResource r = (ApplicationResource) value;
341: Application a = r.getApplication();
342: if (a == null)
343: throw new PaintException(
344: "Application not specified for resorce "
345: + value.getClass().getName());
346: String uri = a.getURL().getPath();
347: if (uri.charAt(uri.length() - 1) != '/')
348: uri += "/";
349: uri += a.getRelativeLocation(r);
350: addAttribute(name, uri);
351:
352: } else if (value instanceof ThemeResource) {
353: addAttribute(name, webAdapterServlet.getResourceLocation(
354: theme.getName(), (ThemeResource) value));
355: } else
356: throw new PaintException("Web adapter does not "
357: + "support resources of type: "
358: + value.getClass().getName());
359:
360: }
361:
362: /** Adds a integer attribute to component.
363: * Atributes must be added before any content is written.
364: *
365: * @param name Attribute name
366: * @param value Attribute value
367: * @return this object
368: */
369: public void addAttribute(String name, int value)
370: throws PaintException {
371: addAttribute(name, String.valueOf(value));
372: }
373:
374: /** Adds a long attribute to component.
375: * Atributes must be added before any content is written.
376: *
377: * @param name Attribute name
378: * @param value Attribute value
379: * @return this object
380: */
381: public void addAttribute(String name, long value)
382: throws PaintException {
383: addAttribute(name, String.valueOf(value));
384: }
385:
386: /** Adds a string attribute to component.
387: * Atributes must be added before any content is written.
388: *
389: * @param name Boolean attribute name
390: * @param value Boolean attribute value
391: * @return this object
392: */
393: public void addAttribute(String name, String value)
394: throws PaintException {
395: // In case of null data output nothing:
396: if ((value == null) || (name == null))
397: throw new NullPointerException(
398: "Parameters must be non-null strings (" + name
399: + "=" + value + ")");
400:
401: //Ensure that the target is open
402: if (this .closed)
403: throw new PaintException(
404: "Attempted to write to a closed PaintTarget.");
405:
406: // Check that argument list is writable.
407: if (!mTagArgumentListOpen)
408: throw new PaintException("XML argument list not open.");
409:
410: tagBuffer.append(" " + name + "=\"" + escapeXML(value) + "\"");
411: }
412:
413: /** Add a string type variable.
414: * @param owner Listener for variable changes
415: * @param name Variable name
416: * @param value Variable initial value
417: * @return Reference to this.
418: */
419: public void addVariable(VariableOwner owner, String name,
420: String value) throws PaintException {
421: String code = variableMap.registerVariable(name, String.class,
422: value, owner);
423: startTag("string");
424: addAttribute(UIDL_ARG_ID, code);
425: addAttribute(UIDL_ARG_NAME, name);
426: addText(value);
427: endTag("string");
428: }
429:
430: /** Add a int type variable.
431: * @param owner Listener for variable changes
432: * @param name Variable name
433: * @param value Variable initial value
434: * @return Reference to this.
435: */
436: public void addVariable(VariableOwner owner, String name, int value)
437: throws PaintException {
438: String code = variableMap.registerVariable(name, Integer.class,
439: new Integer(value), owner);
440: startTag("integer");
441: addAttribute(UIDL_ARG_ID, code);
442: addAttribute(UIDL_ARG_NAME, name);
443: addAttribute(UIDL_ARG_VALUE, String.valueOf(value));
444: endTag("integer");
445: }
446:
447: /** Add a boolean type variable.
448: * @param owner Listener for variable changes
449: * @param name Variable name
450: * @param value Variable initial value
451: * @return Reference to this.
452: */
453: public void addVariable(VariableOwner owner, String name,
454: boolean value) throws PaintException {
455: String code = variableMap.registerVariable(name, Boolean.class,
456: new Boolean(value), owner);
457: startTag("boolean");
458: addAttribute(UIDL_ARG_ID, code);
459: addAttribute(UIDL_ARG_NAME, name);
460: addAttribute(UIDL_ARG_VALUE, String.valueOf(value));
461: endTag("boolean");
462: }
463:
464: /** Add a string array type variable.
465: * @param owner Listener for variable changes
466: * @param name Variable name
467: * @param value Variable initial value
468: * @return Reference to this.
469: */
470: public void addVariable(VariableOwner owner, String name,
471: String[] value) throws PaintException {
472: String code = variableMap.registerVariable(name,
473: String[].class, value, owner);
474: startTag("array");
475: addAttribute(UIDL_ARG_ID, code);
476: addAttribute(UIDL_ARG_NAME, name);
477: for (int i = 0; i < value.length; i++)
478: addSection("ai", value[i]);
479: endTag("array");
480: }
481:
482: /** Add a upload stream type variable.
483: * @param owner Listener for variable changes
484: * @param name Variable name
485: * @param value Variable initial value
486: * @return Reference to this.
487: */
488: public void addUploadStreamVariable(VariableOwner owner, String name)
489: throws PaintException {
490: String code = variableMap.registerVariable(name,
491: UploadStream.class, null, owner);
492: startTag("uploadstream");
493: addAttribute(UIDL_ARG_ID, code);
494: addAttribute(UIDL_ARG_NAME, name);
495: endTag("uploadstream");
496: }
497:
498: /** Print single text section.
499: *
500: * Prints full text section. The section data is escaped from XML tags and
501: * surrounded by XML start and end-tags.
502: */
503: public void addSection(String sectionTagName, String sectionData)
504: throws PaintException {
505: startTag(sectionTagName);
506: addText(sectionData);
507: endTag(sectionTagName);
508: }
509:
510: /** Add XML dirctly to UIDL */
511: public void addUIDL(String xml) throws PaintException {
512:
513: //Ensure that the target is open
514: if (this .closed)
515: throw new PaintException(
516: "Attempted to write to a closed PaintTarget.");
517:
518: // Make sure that the open start tag is closed before
519: // anything is written.
520: ensureClosedTag();
521:
522: // Escape and write what was given
523: if (xml != null)
524: append(xml);
525:
526: }
527:
528: /** Add XML section with namespace
529: * @see org.millstone.base.terminal.PaintTarget#addXMLSection(String, String, String)
530: */
531: public void addXMLSection(String sectionTagName,
532: String sectionData, String namespace) throws PaintException {
533:
534: //Ensure that the target is open
535: if (this .closed)
536: throw new PaintException(
537: "Attempted to write to a closed PaintTarget.");
538:
539: startTag(sectionTagName);
540: if (namespace != null)
541: addAttribute("xmlns", namespace);
542:
543: // Close that starting tag
544: ensureClosedTag();
545:
546: if (sectionData != null)
547: append(sectionData);
548: endTag(sectionTagName);
549: }
550:
551: /** Get the UIDL already printed to stream.
552: * Paint target must be closed before the getUIDL()
553: * cn be called.
554: */
555: public String getUIDL() {
556: if (this .closed) {
557: return uidlBuffer.toString();
558: }
559: throw new IllegalStateException(
560: "Tried to read UIDL from open PaintTarget");
561: }
562:
563: /** Close the paint target.
564: * Paint target must be closed before the getUIDL()
565: * cn be called.
566: * Subsequent attempts to write to paint target.
567: * If the target was already closed, call to this
568: * function is ignored.
569: * will generate an exception.
570: */
571: public void close() throws PaintException {
572: if (!this .closed) {
573: this .endTag("uidl");
574: this .closed = true;
575: }
576: }
577:
578: /** Print element start tag of a paintable section.
579: * Starts a paintable section using the given tag. The PaintTarget may
580: * implement a caching scheme, that checks the paintable has actually
581: * changed or can a cached version be used instead. This method should call
582: * the startTag method. <p> If the Paintable is found in cache and this
583: * function returns true it may omit the content and close the tag, in which
584: * case cached content should be used.
585: * </p><b>Note:</b> Web adapter does not currently implement caching and
586: * this function always returns false.
587: * @param paintable The paintable to start
588: * @param tagName The name of the start tag
589: * @return false
590: * @see org.millstone.base.terminal.PaintTarget#startTag(Paintable, String),
591: * #startTag(String)
592: * @since 3.1
593: */
594: public boolean startTag(Paintable paintable, String tag)
595: throws PaintException {
596: startTag(tag);
597: return false;
598: }
599:
600: /** Add CDATA node to target UIDL-tree.
601: * @param text Character data to add
602: * @since 3.1
603: */
604: public void addCharacterData(String text) throws PaintException {
605: addUIDL("<![CDATA[" + text + "]]>");
606: }
607:
608: }
|