001: /*
002: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003: *
004: * "The contents of this file are subject to the Mozilla Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
011: * License for the specific language governing rights and limitations under
012: * the License.
013: *
014: * The Original Code is ICEfaces 1.5 open source software code, released
015: * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
016: * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
017: * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
018: *
019: * Contributor(s): _____________________.
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
023: * License), in which case the provisions of the LGPL License are
024: * applicable instead of those above. If you wish to allow use of your
025: * version of this file only under the terms of the LGPL License and not to
026: * allow others to use your version of this file under the MPL, indicate
027: * your decision by deleting the provisions above and replace them with
028: * the notice and other provisions required by the LGPL License. If you do
029: * not delete the provisions above, a recipient may use your version of
030: * this file under either the MPL or the LGPL License."
031: *
032: */
033:
034: /*
035: * $Id: DOMContext.java,v 1.0 2004/07/20 14:02:36 tedg Exp $
036: */
037: package com.icesoft.faces.context;
038:
039: import com.icesoft.faces.util.HalterDump;
040: import com.icesoft.faces.webapp.http.common.Configuration;
041: import com.icesoft.faces.webapp.http.common.ConfigurationException;
042: import org.w3c.dom.DOMException;
043: import org.w3c.dom.Document;
044: import org.w3c.dom.Element;
045: import org.w3c.dom.NamedNodeMap;
046: import org.w3c.dom.Node;
047: import org.w3c.dom.NodeList;
048: import org.w3c.dom.Text;
049:
050: import javax.faces.component.UIComponent;
051: import javax.faces.context.FacesContext;
052: import javax.faces.context.ResponseWriter;
053: import java.io.IOException;
054: import java.util.ArrayList;
055: import java.util.HashMap;
056: import java.util.List;
057: import java.util.Map;
058:
059: /**
060: * <p><strong>DOMContext</strong> provides a component specific interface to the
061: * DOM renderer
062: */
063: public class DOMContext implements java.io.Serializable {
064: private transient DOMResponseWriter writer;
065: private Node cursor;
066: private Document document;
067: private Node rootNode;
068: private Node parentElement;
069: private boolean initialized;
070:
071: protected DOMContext(DOMResponseWriter writer, Document document,
072: Node parentElement) {
073: this .writer = writer;
074: this .document = document;
075: this .cursor = parentElement;
076: this .parentElement = parentElement;
077: this .initialized = false;
078: }
079:
080: /**
081: * <p>Determine whether this instance is initialized. An initialized
082: * instance is guaranteed to have a root node.</p>
083: *
084: * @return boolean reflecting whether this instance is initialized.
085: */
086: public boolean isInitialized() {
087: return initialized;
088: }
089:
090: /**
091: * <p>This method returns the DOMContext associated with the specified
092: * component.</p>
093: *
094: * @param facesContext an instance of {@link FacesContext} associated with
095: * the lifecycle
096: * @param component component associated with this {@link DOMContext}
097: * @return the attached {@link DOMContext}
098: */
099: public static DOMContext attachDOMContext(
100: FacesContext facesContext, UIComponent component) {
101: ResponseWriter responseWriter = facesContext
102: .getResponseWriter();
103: DOMResponseWriter domWriter;
104: if (responseWriter instanceof DOMResponseWriter) {
105: domWriter = (DOMResponseWriter) responseWriter;
106: } else {
107: domWriter = createTemporaryDOMResponseWriter(
108: responseWriter, facesContext);
109: }
110: Node cursorParent = domWriter.getCursorParent();
111: Document doc = domWriter.getDocument();
112: Map domContexts = domWriter.getDomResponseContexts();
113:
114: DOMContext context = null;
115: String clientId = component.getClientId(FacesContext
116: .getCurrentInstance());
117: if (clientId != null && domContexts.containsKey(clientId)) {
118: context = (DOMContext) domContexts.get(clientId);
119: }
120: if (null == context) {
121: context = new DOMContext(domWriter, doc, cursorParent);
122: domContexts.put(clientId, context);
123: }
124: //context may have been severed from the tree at some point
125: if (context.isInitialized()) {
126: if (!(cursorParent instanceof Element)) {
127: context.stepOver();
128: return context;
129: }
130: context.attach((Element) cursorParent);
131: }
132: context.stepOver();
133:
134: return context;
135: }
136:
137: private static DOMResponseWriter createTemporaryDOMResponseWriter(
138: ResponseWriter responseWriter, FacesContext facesContext) {
139: DOMResponseWriter domWriter;
140: domWriter = new DOMResponseWriter(facesContext, null,
141: new Configuration() {
142: public String getName() {
143: return "noop configuration";
144: }
145:
146: public Configuration getChild(String child)
147: throws ConfigurationException {
148: throw new ConfigurationException(
149: "child not available");
150: }
151:
152: public Configuration[] getChildren(String name)
153: throws ConfigurationException {
154: throw new ConfigurationException(
155: "children not available");
156: }
157:
158: public String getAttribute(String paramName)
159: throws ConfigurationException {
160: throw new ConfigurationException(
161: "attribute not available");
162: }
163:
164: public String getValue()
165: throws ConfigurationException {
166: throw new ConfigurationException(
167: "value not available");
168: }
169: });
170: Document doc = domWriter.getDocument();
171: Element html = doc.createElement("html");
172: doc.appendChild(html);
173: Element body = doc.createElement("body");
174: html.appendChild(body);
175: domWriter.setCursorParent(body);
176: return domWriter;
177: }
178:
179: /**
180: * <p>Get the DOMContext associated with the component. Do not attach the
181: * DOMContext instance to its parent element.</p>
182: *
183: * @param facesContext
184: * @param component the {@link UIComponent} instance whose DOMContext we
185: * are retrieving
186: * @return {@link DOMContext}
187: */
188: public static DOMContext getDOMContext(FacesContext facesContext,
189: UIComponent component) {
190: ResponseWriter responseWriter = facesContext
191: .getResponseWriter();
192: DOMResponseWriter domWriter;
193: if (responseWriter instanceof DOMResponseWriter) {
194: domWriter = (DOMResponseWriter) responseWriter;
195: } else {
196: domWriter = createTemporaryDOMResponseWriter(
197: responseWriter, facesContext);
198: }
199: Document doc = domWriter.getDocument();
200: Map domContexts = domWriter.getDomResponseContexts();
201:
202: DOMContext context = null;
203: String clientId = component.getClientId(FacesContext
204: .getCurrentInstance());
205: if (domContexts.containsKey(clientId)) {
206: context = (DOMContext) domContexts.get(clientId);
207: }
208: if (null == context) {
209: Node cursorParent = domWriter.getCursorParent();
210: context = new DOMContext(domWriter, doc, cursorParent);
211: domContexts.put(clientId, context);
212: }
213: return context;
214: }
215:
216: private void attach(Element cursorParent) {
217: if (null == rootNode) { //nothing to attach
218: return;
219: }
220: if (rootNode.equals(cursorParent)) {
221: return;
222: }
223:
224: //TODO needs proper fix
225: //Quick & temp fix for ICEfacesWebPresentation application
226: //This exception only happens when "rootNode" is ancestor of "cursor"
227: if (rootNode.getParentNode() != cursorParent) {
228: try {
229: //re-attaching on top of another node
230: //replace them and assume they will re-attach later
231: cursorParent.appendChild(rootNode);
232: } catch (DOMException e) {
233: //this happens in strea-write mode only.
234: }
235: }
236: }
237:
238: /**
239: * <p>Creates an element of the type specified. Note that the instance
240: * returned implements the <code>Element</code> interface, so attributes can
241: * be specified directly on the returned object. <br>In addition, if there
242: * are known attributes with default values, <code>Attr</code> nodes
243: * representing them are automatically created and attached to the
244: * element.</p>
245: *
246: * @param name the specified Element type to create
247: * @return the created element
248: */
249: public Element createElement(String name) {
250: return document.createElement(name);
251: }
252:
253: /**
254: * <p/>
255: * Creates a <code>Text</code> node given the specified string. </p>
256: *
257: * @param cData The data for the node.
258: * @return The new <code>Text</code> object.
259: */
260: public Text createTextNode(String cData) {
261: return document.createTextNode(cData);
262: }
263:
264: /**
265: * <p/>
266: * Set the rootNode member variable to the parameter Node. </p>
267: *
268: * @param rootNode
269: */
270: public void setRootNode(Node rootNode) {
271: this .rootNode = rootNode;
272: parentElement.appendChild(rootNode);
273: initialized = true;
274: }
275:
276: /**
277: * <p/>
278: * Creates an element of the type specified. Note that the instance returned
279: * implements the <code>Element</code> interface, so attributes can be
280: * specified directly on the returned object. <br>In addition, if there are
281: * known attributes with default values, <code>Attr</code> nodes
282: * representing them are automatically created and attached to the element.
283: * Set the rootNode member variable of this instance to the newly-created
284: * Element. </p>
285: *
286: * @param name
287: * @return Element
288: */
289: public Element createRootElement(String name) {
290: Element rootElement = createElement(name);
291: setRootNode(rootElement);
292: return rootElement;
293: }
294:
295: void setIsolatedRootNode(Node rootElement) {
296: this .rootNode = rootElement;
297: initialized = true;
298: }
299:
300: /**
301: * <p>Get the rootNode member variable.</p>
302: *
303: * @return rootNode the root node of this <code>DOMContext</code> instance
304: */
305: public Node getRootNode() {
306: return rootNode;
307: }
308:
309: /**
310: * Set the position at which the next rendered node will be appended
311: *
312: * @param cursorParent
313: */
314: public void setCursorParent(Node cursorParent) {
315: this .cursor = cursorParent;
316: writer.setCursorParent(cursorParent);
317: }
318:
319: /**
320: * Get the position in the document where the next DOM node will be
321: * rendererd.
322: */
323: public Node getCursorParent() {
324: return cursor;
325: }
326:
327: /**
328: * Maintain the cursor and cursor position; step to the position where the
329: * next sibling should be rendered.
330: */
331: public void stepOver() {
332: if (null != rootNode && rootNode.getParentNode() != null) {
333: setCursorParent(rootNode.getParentNode());
334: }
335: }
336:
337: /**
338: * Maintain the cursor and cursor such that the next rendered component will
339: * be rendered as a child of the parameter component.
340: *
341: * @param component
342: */
343: public void stepInto(UIComponent component) {
344: if (rootNode != null) {
345: // default behaviour;
346: // just like calling setCursorParent at the end of encode begin
347: setCursorParent(rootNode);
348: }
349: }
350:
351: /**
352: * Retrieve the org.w3c.dom.Document instance associated with this
353: * DOMContext
354: *
355: * @return Document
356: */
357: public Document getDocument() {
358: return document;
359: }
360:
361: /**
362: * Remove all children from Node parent
363: *
364: * @param parent - the root node to remove
365: */
366: public static void removeChildren(Node parent) {
367: while (parent.hasChildNodes()) {
368: parent.removeChild(parent.getFirstChild());
369: }
370: }
371:
372: /**
373: * Removes from the root element all children with node name equal to the
374: * nodeName parameter
375: *
376: * @param rootElement
377: * @param name
378: */
379: public static void removeChildrenByTagName(Element rootElement,
380: String name) {
381:
382: Node nextChildToRemove = null;
383: while (rootElement.hasChildNodes()
384: && ((nextChildToRemove = findChildWithNodeName(
385: rootElement, name)) != null)) {
386: rootElement.removeChild(nextChildToRemove);
387: }
388: }
389:
390: /**
391: * Find and return root's child Node with name nodeName or null if no such
392: * child Node exists.
393: */
394: private static Node findChildWithNodeName(Element root,
395: String nodeName) {
396: NodeList children = root.getChildNodes();
397: int length = children.getLength();
398: for (int i = 0; i < length; i++) {
399: Node nextChildNode = children.item(i);
400: String name = nextChildNode.getNodeName();
401: if (name.equalsIgnoreCase(nodeName)) {
402: return nextChildNode;
403: }
404: }
405: return null;
406: }
407:
408: public static List findChildrenWithNodeName(Element root,
409: String nodeName) {
410: NodeList children = root.getChildNodes();
411: int length = children.getLength();
412: List foundItems = new ArrayList();
413: for (int i = 0; i < length; i++) {
414: Node nextChildNode = children.item(i);
415: String name = nextChildNode.getNodeName();
416: if (name.equalsIgnoreCase(nodeName)) {
417: foundItems.add(nextChildNode);
418: }
419: }
420: return foundItems;
421: }
422:
423: private HashMap halterDumps = new HashMap();
424:
425: /**
426: * <p>Writes the DOM subtree anchored at <code>root</code> to the current
427: * ResponseWriter. Serialization is halted at the node <code>halter</code>
428: * (writing only the opening tag) and will be resumed from this node on the
429: * next call to this function.</p>
430: *
431: * @param facesContext current FacesContext
432: * @param component JSF component being rendered
433: * @param root node indicating subtree of DOM to eventually
434: * serialize
435: * @param halter node upon which to halt serialization on this pass
436: */
437: public void streamWrite(FacesContext facesContext,
438: UIComponent component, Node root, Node halter)
439: throws IOException {
440: if (!DOMResponseWriter.isStreamWriting()) {
441: return;
442: }
443: HalterDump halterDump = (HalterDump) halterDumps.get(root);
444: if (null == halterDump) {
445: halterDump = new HalterDump(facesContext
446: .getResponseWriter(), component, root);
447: halterDumps.put(root, halterDump);
448: }
449: halterDump.streamWrite(halter);
450: }
451:
452: /**
453: * <p>Convenience method for <code>streamWrite(facesContext, component,
454: * this.rootNode, null)</code>.</p>
455: *
456: * @param facesContext current FacesContext
457: * @param component JSF component being rendered
458: */
459: public void streamWrite(FacesContext facesContext,
460: UIComponent component) throws IOException {
461: streamWrite(facesContext, component, rootNode, null);
462: }
463:
464: /**
465: * <p>Convenience method used by renderers to determine if Stream writing is
466: * enabled.
467: */
468: public boolean isStreamWriting() {
469: return DOMResponseWriter.isStreamWriting();
470: }
471:
472: /**
473: * This method can be used as an alternative to the streamWrite method. When
474: * using this method you must also use the endNode method.
475: */
476: public void startNode(FacesContext facesContext,
477: UIComponent component, Node node) throws IOException {
478: if (!isStreamWriting()) {
479: return;
480: } else {
481: // get writer
482: ResponseWriter writer = facesContext.getResponseWriter();
483:
484: // write by node type
485: switch (node.getNodeType()) {
486:
487: case Node.DOCUMENT_NODE:
488: break;
489:
490: case Node.ELEMENT_NODE:
491: // start the element
492: writer.startElement(node.getNodeName().toLowerCase(),
493: component);
494: // write attributes
495: NamedNodeMap attributes = node.getAttributes();
496:
497: for (int i = 0; i < attributes.getLength(); i++) {
498: Node current = attributes.item(i);
499: writer.writeAttribute(current.getNodeName(),
500: current.getNodeValue(), current
501: .getNodeName());
502: }
503: break;
504:
505: case Node.TEXT_NODE:
506: writer.writeText(node.getNodeValue(), "text");
507: break;
508: }
509: }
510: }
511:
512: /**
513: * This method can be used as an alternative to the streamWrite method. When
514: * using this method you must also use the startNode method.
515: */
516: public void endNode(FacesContext facesContext,
517: UIComponent component, Node node) throws IOException {
518: if (!isStreamWriting()) {
519: return;
520: } else {
521: // get writer
522: ResponseWriter writer = facesContext.getResponseWriter();
523: switch (node.getNodeType()) {
524:
525: case Node.DOCUMENT_NODE:
526: break;
527:
528: case Node.ELEMENT_NODE:
529: writer.endElement(node.getNodeName().toLowerCase());
530: break;
531:
532: case Node.TEXT_NODE:
533: break;
534: }
535: }
536: }
537:
538: }
|