0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.visualweb.insync.faces;
0042:
0043: import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eeModule;
0044: import org.netbeans.modules.visualweb.api.designer.markup.MarkupService;
0045: import org.netbeans.modules.visualweb.insync.InSyncServiceProvider;
0046: import org.netbeans.modules.visualweb.insync.Util;
0047: import org.netbeans.modules.visualweb.insync.java.JavaUnit;
0048: import org.netbeans.modules.visualweb.insync.java.Statement;
0049: import org.netbeans.modules.visualweb.insync.models.FacesModel;
0050: import org.netbeans.modules.visualweb.insync.models.FacesModelSet;
0051: import java.beans.BeanInfo;
0052: import java.beans.PropertyChangeEvent;
0053: import java.beans.PropertyChangeListener;
0054: import java.util.ArrayList;
0055: import java.util.HashMap;
0056: import java.util.Iterator;
0057: import java.util.List;
0058: import java.util.Map;
0059: import java.util.WeakHashMap;
0060: import javax.faces.component.UIComponent;
0061: import javax.faces.component.UIForm;
0062: import javax.faces.component.UIViewRoot;
0063: import javax.faces.context.FacesContext;
0064: import org.netbeans.modules.visualweb.jsfsupport.container.JsfTagSupportException;
0065: import org.netbeans.modules.visualweb.project.jsf.api.JsfProjectUtils;
0066: import org.openide.ErrorManager;
0067: import org.openide.loaders.DataObject;
0068:
0069: import org.netbeans.modules.visualweb.extension.openide.util.Trace;
0070: import org.openide.util.Exceptions;
0071: import org.w3c.dom.Document;
0072: import org.w3c.dom.DocumentFragment;
0073: import org.w3c.dom.Element;
0074: import org.w3c.dom.Node;
0075: import org.w3c.dom.NodeList;
0076:
0077: import com.sun.rave.designtime.DesignBean;
0078: import com.sun.rave.designtime.DesignInfo;
0079: import com.sun.rave.designtime.DesignProperty;
0080: import com.sun.rave.designtime.Position;
0081: import com.sun.rave.designtime.markup.MarkupDesignBean;
0082: import com.sun.rave.designtime.markup.MarkupDesignInfo;
0083: import com.sun.rave.designtime.markup.MarkupPosition;
0084: import com.sun.rave.designtime.markup.MarkupRenderContext;
0085: import com.sun.rave.designtime.markup.MarkupMouseRegion;
0086: import org.netbeans.modules.visualweb.insync.Model;
0087: import org.netbeans.modules.visualweb.insync.ParserAnnotation;
0088: import org.netbeans.modules.visualweb.insync.UndoEvent;
0089: import org.netbeans.modules.visualweb.insync.beans.Bean;
0090: import org.netbeans.modules.visualweb.insync.beans.Property;
0091: import org.netbeans.modules.visualweb.insync.live.BeansDesignBean;
0092: import org.netbeans.modules.visualweb.insync.live.LiveUnit;
0093: import org.netbeans.modules.visualweb.insync.markup.MarkupUnit;
0094: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
0095: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
0096: import org.netbeans.modules.visualweb.jsfsupport.container.FacesContainer;
0097: import org.w3c.dom.Text;
0098:
0099: /**
0100: * An extended FacesUnit that adds in the ability to have a JSF bean being designed have source in
0101: * JSPX in addition to the regular Java source.
0102: *
0103: * @author cquinn
0104: */
0105: public class FacesPageUnit extends FacesUnit implements
0106: PropertyChangeListener {
0107:
0108: public static final String URI_JSP_PAGE = "http://java.sun.com/JSP/Page";
0109: public static final String URI_JSF_CORE = "http://java.sun.com/jsf/core";
0110: public static final String URI_JSF_HTML = "http://java.sun.com/jsf/html";
0111:
0112: final UIViewRoot viewRoot; // our reusable Faces view tree root instance
0113:
0114: protected MarkupUnit pgunit; // the page definition markup unit
0115: Document document; // the current DOM document from the unit
0116: String defaultSrcEncoding = "UTF-8"; // the default source encoding
0117: String defaultEncoding = "UTF-8"; // the default (response) encoding
0118: String defaultLanguage; // the default (response) language
0119:
0120: private MarkupBean defaultParent; // the current default parent for new faces beans
0121: private DesignBean preRendered; // A design bean we've pre-rendered
0122: private DocumentFragment preRenderedFragment; // For the preRendered bean, use this fragment
0123: private Exception renderFailure; // Most recent render-failure exception
0124: private DesignBean renderFailureComponent; // Most recent render-failure component
0125: protected DataObject pageUnitDataObject;
0126:
0127: //--------------------------------------------------------------------------------- Construction
0128:
0129: /**
0130: * Construct an FacesPageUnit from existing java and markup units
0131: *
0132: * @param junit
0133: * @param cl
0134: * @param pkgname
0135: * @param rootPackage
0136: * @param container
0137: * @param pgunit
0138: */
0139: public FacesPageUnit(JavaUnit junit, ClassLoader cl,
0140: String pkgname, Model model, String rootPackage,
0141: FacesContainer container, MarkupUnit pgunit) {
0142: super (junit, cl, pkgname, model, rootPackage, container);
0143: this .pgunit = pgunit;
0144: viewRoot = container.newViewRoot();
0145: //Trace.enableTraceCategory("insync.faces");
0146: // Storing the dataObject such that we can remove the property change listener later in destroy
0147: // order of destroy may mean that the page unit no longer can get access to its data object
0148: pageUnitDataObject = pgunit.getDataObject();
0149: pageUnitDataObject.addPropertyChangeListener(this );
0150: }
0151:
0152: /*
0153: * @see org.netbeans.modules.visualweb.insync.Unit#destroy()
0154: */
0155: public void destroy() {
0156: if (pageUnitDataObject != null) {
0157: pageUnitDataObject.removePropertyChangeListener(this );
0158: }
0159: pgunit = null;
0160: pageUnitDataObject = null;
0161: document = null;
0162: super .destroy();
0163: }
0164:
0165: //----------------------------------------------------------------------------------------- Unit
0166:
0167: /*
0168: * @see org.netbeans.modules.visualweb.insync.Unit#writeLock(org.netbeans.modules.visualweb.insync.UndoEvent)
0169: */
0170: public void writeLock(UndoEvent event) {
0171: pgunit.writeLock(event);
0172: try {
0173: super .writeLock(event);
0174: } catch (IllegalStateException e) {
0175: // if the second lock bombed, the undo our first lock
0176: pgunit.writeUnlock(event);
0177: throw e;
0178: }
0179: }
0180:
0181: /*
0182: * @see org.netbeans.modules.visualweb.insync.Unit#writeUnlock(org.netbeans.modules.visualweb.insync.UndoEvent)
0183: */
0184: public boolean writeUnlock(UndoEvent event) {
0185: boolean unlocked = super .writeUnlock(event);
0186: unlocked |= pgunit.writeUnlock(event);
0187: return unlocked;
0188: }
0189:
0190: /*
0191: * @see org.netbeans.modules.visualweb.insync.Unit#isWriteLocked()
0192: */
0193: public boolean isWriteLocked() {
0194: return pgunit.isWriteLocked() || super .isWriteLocked();
0195: }
0196:
0197: /**
0198: * @return the combined state of this unit. A dirty state overrules clean. The only illegal
0199: * state is one DirtySource and one DirtyModel
0200: * @see org.netbeans.modules.visualweb.insync.Unit#getState()
0201: */
0202: public State getState() {
0203: State beanstate = super .getState();
0204: State pgstate = pgunit.getState();
0205: if (pgstate == beanstate)
0206: return beanstate;
0207: if (beanstate == State.BUSTED || pgstate == State.BUSTED)
0208: return State.BUSTED;
0209: if (beanstate == State.CLEAN)
0210: return pgstate;
0211: if (pgstate == State.CLEAN)
0212: return beanstate;
0213: if (pgstate == State.MODELDIRTY)
0214: return State.MODELDIRTY;
0215: // this is a bad mix and should never happen
0216: throw new IllegalStateException("Illegal state mix " + this
0217: + "(" + beanstate + ") and " + pgunit + "(" + pgstate
0218: + ")");
0219: }
0220:
0221: /*
0222: * @see org.netbeans.modules.visualweb.insync.Unit#getErrors()
0223: */
0224: public ParserAnnotation[] getErrors() {
0225: ParserAnnotation[] pgErrors = pgunit.getErrors();
0226: ParserAnnotation[] beansErrors = super .getErrors();
0227: int errorCount = pgErrors.length + beansErrors.length;
0228: if (errorCount == 0)
0229: return beansErrors;
0230:
0231: // Must combine
0232: ParserAnnotation[] errors = new ParserAnnotation[errorCount];
0233: int index = 0;
0234: System.arraycopy(pgErrors, 0, errors, index, pgErrors.length);
0235: index += pgErrors.length;
0236: System.arraycopy(beansErrors, 0, errors, index,
0237: beansErrors.length);
0238: index += beansErrors.length;
0239: return errors;
0240: }
0241:
0242: /*
0243: * @see org.netbeans.modules.visualweb.insync.Unit#readLock()
0244: */
0245: public void readLock() {
0246: pgunit.readLock();
0247: super .readLock();
0248: }
0249:
0250: /*
0251: * @see org.netbeans.modules.visualweb.insync.Unit#readUnlock()
0252: */
0253: public void readUnlock() {
0254: super .readUnlock();
0255: pgunit.readUnlock();
0256: }
0257:
0258: protected boolean syncSubUnits() {
0259: // read supporting dom document first, then our bean definitions since that will call us
0260: // back during binding
0261: boolean synced = pgunit.sync();
0262: // call the internal justSync method so that we can manage scan & bind ourselves
0263: synced |= super .syncSubUnits();
0264: return synced;
0265: }
0266:
0267: //------------------------------------------------------------------------------------ BeansUnit
0268:
0269: /**
0270: * Scan our document to find or create our outer tracked elements
0271: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#scan()
0272: */
0273: protected void scan() {
0274: super .scan(); // make sure class & other base java stuff are retrieved first
0275: // Now go through and find or create our JSP elements
0276: document = pgunit.getSourceDom();
0277: // if (isPage())
0278: // ensurePageStructure();
0279: // else
0280: // ensureFragmentStructure();
0281: }
0282:
0283: /**
0284: * Determine if the current document is a complete page (true) or a fragment (false)
0285: *
0286: * @return true iff the current document is a complete page
0287: */
0288: public boolean isPage() {
0289: return document.getDocumentElement().getTagName().equals(
0290: "jsp:root");
0291: }
0292:
0293: /**
0294: * For complete pages, make sure that the outer JSPX structure is correct.
0295: */
0296: private void ensurePageStructure() {
0297: // Get or create the JSP root element. All other elements are children of this
0298: Element root = MarkupUnit.ensureRoot(document, "jsp:root");
0299:
0300: // Make sure the NS URIs are present, supplying default prefixes in case they aren't
0301: String jspPre = pgunit.getNamespacePrefix(URI_JSP_PAGE, "jsp");
0302: String facesPre = pgunit.getNamespacePrefix(URI_JSF_CORE, "f");
0303: pgunit.getNamespacePrefix(URI_JSF_HTML, "h");
0304:
0305: // We require JSP 1.2
0306: String projectVersion = JsfProjectUtils.getProjectVersion(model
0307: .getProject());
0308: if (projectVersion.equals(J2eeModule.J2EE_13)
0309: || projectVersion.equals(J2eeModule.J2EE_14)) {
0310: pgunit.ensureAttributeValue(root, "version", "1.2");
0311: } else if (projectVersion.equals(J2eeModule.JAVA_EE_5)) {
0312: pgunit.ensureAttributeValue(root, "version", "2.1");
0313: }
0314:
0315: // Get or create (as first child) a JSP page directive element to control page type and
0316: // source charset encoding. Use the page' xml encoding if available, else use our default
0317: // for both.
0318: Element pgdirective = MarkupUnit.ensureElement(root, jspPre
0319: + ":directive.page", null);
0320: String srcEncoding = pgunit.getEncoding();
0321: if (srcEncoding == null || srcEncoding.length() == 0)
0322: srcEncoding = pgdirective.getAttribute("pageEncoding");
0323: if (srcEncoding == null || srcEncoding.length() == 0)
0324: srcEncoding = defaultSrcEncoding;
0325: pgunit.ensureAttributeValue(pgdirective, "pageEncoding",
0326: srcEncoding);
0327:
0328: // set the page response content type and charset (encoding)
0329: String encoding = defaultEncoding;
0330: pgunit.ensureAttributeExists(pgdirective, "contentType",
0331: "text/html;charset=" + encoding);
0332:
0333: // Get or create the Faces view element, which is the root of all other faces UIComponent
0334: // elements
0335: Element view = MarkupUnit.ensureElement(root, facesPre
0336: + ":view", pgdirective);
0337:
0338: // The following code was used in Creator 1.0: it goes and ENFORCES
0339: // a particular page structure: <html><body><h:form> (with various
0340: // attributes on these tags.
0341: // This is no longer a required page structure - and in fact our
0342: // new default pages, which use the Sun WEB UI components (braveheart)
0343: // do not follow it.
0344: // ensureHtmlPageStructure(view);
0345: }
0346:
0347: private void ensureHtmlPageStructure(Element view) {
0348: // Get or create the basic xhtml elements
0349: Element html = MarkupUnit.ensureElement(view,
0350: HtmlTag.HTML.name, null);
0351: pgunit.ensureAttributeExists(html, HtmlAttribute.LANG,
0352: defaultLanguage);
0353: pgunit.ensureAttributeExists(html, "xml:lang", defaultLanguage);
0354:
0355: Element head = MarkupUnit.ensureElement(html,
0356: HtmlTag.HEAD.name, null);
0357:
0358: // Add this meta/http-equiv tag as a hint to browsers...
0359: //!CQ now relying on jsp:directive.page
0360: /*
0361: Element ctmeta = MarkupUnit.getDescendantElementByAttr(head, HtmlTag.META.name, "http-equiv", "Content-type");
0362: if (ctmeta == null) {
0363: ctmeta = pgunit.addElement(head, null, null, null, HtmlTag.META.name);
0364: pgunit.ensureAttributeValue(ctmeta, "http-equiv", "Content-type");
0365: pgunit.ensureAttributeValue(ctmeta, "content", "text/html;charset=" + encoding);
0366: }*/
0367:
0368: Element title = MarkupUnit.ensureElement(head,
0369: HtmlTag.TITLE.name, null);
0370: if (MarkupUnit.getElementText(title).equals("__TITLE__"))
0371: MarkupUnit.setElementText(title, getBeanName() + " Title");
0372:
0373: // If there is no body, add a default one in grid layout mode
0374: if (MarkupUnit.getFirstDescendantElement(html,
0375: HtmlTag.BODY.name) == null
0376: && MarkupUnit.getFirstDescendantElement(html,
0377: HtmlTag.FRAMESET.name) == null) {
0378: Element body = MarkupUnit.ensureElement(html,
0379: HtmlTag.BODY.name, head);
0380: body
0381: .setAttribute(HtmlAttribute.STYLE,
0382: "-rave-layout: grid"); // NOI18N
0383: }
0384: }
0385:
0386: /**
0387: * For page fragments, make sure that there is an outer div.
0388: */
0389: private void ensureFragmentStructure() {
0390: // Get or create the JSP root element. All other elements are children of this
0391: Element root = MarkupUnit
0392: .ensureRoot(document, HtmlTag.DIV.name);
0393: }
0394:
0395: /**
0396: * Bind all markup beans to their element and take care of parenting. Creates the HtmlBean
0397: * instances for HTML and other fake beans on the fly
0398: */
0399:
0400: // This is a horrible O(N2) algorithm, need to change it - Winston
0401: protected void bindMarkupBeans(Element e) {
0402: MarkupBean mbean = getMarkupBean(e);
0403: if (mbean == null) {
0404: String type = HtmlBean.getBeanClassname(e);
0405: if (type != null) {
0406: BeanInfo bi = getBeanInfo(type);
0407: if (bi != null) {
0408: mbean = new HtmlBean(this , bi, e.getTagName(), e);
0409: beans.add(mbean);
0410: }
0411: } else {
0412: // OK, there are no bindings in the Java, create designbean from tag
0413: String tagName = e.getLocalName();
0414: String name = e.getAttribute(FacesBean.ID_ATTR);
0415: String tagLibUri = pgunit.findTaglibUri(e.getPrefix());
0416: try {
0417: type = container.findComponentClass(tagName,
0418: tagLibUri);
0419: assert Trace.trace("insync.faces",
0420: "FU.bindMarkupBeans type:" + type + " tag:"
0421: + tagName + " tagLibUri:"
0422: + tagLibUri);
0423: BeanInfo bi = getBeanInfo(type);
0424: mbean = new FacesBean(this , bi, name, e);
0425: // snag the form bean as it goes by for later use as the default parent
0426: if (defaultParent == null
0427: && tagName.equals(HtmlTag.FORM.name)) {
0428: defaultParent = mbean;
0429: }
0430: beans.add(mbean);
0431: } catch (JsfTagSupportException ex) {
0432: ErrorManager.getDefault().log(
0433: ex.getLocalizedMessage());
0434: }
0435: }
0436: }
0437: if (mbean != null) {
0438: Bean parent = mbean.bindParent();
0439: if (parent != null)
0440: parent.addChild(mbean, null); // append is right since we are walking the DOM here
0441: }
0442:
0443: NodeList ekids = e.getChildNodes();
0444: int ekidcount = ekids.getLength();
0445: for (int i = 0; i < ekidcount; i++) {
0446: Node n = ekids.item(i);
0447: if (n.getNodeType() == Node.ELEMENT_NODE)
0448: bindMarkupBeans((Element) n);
0449: }
0450: }
0451:
0452: /*
0453: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#bind()
0454: */
0455: protected void bind() {
0456: // with a new document, we'll need to recompute our defaultParent
0457: defaultParent = null;
0458:
0459: // scan our markup document to wire up the bean tree to match the DOM
0460: bindMarkupBeans(document.getDocumentElement());
0461:
0462: super .bind();
0463:
0464: // Find a suitable default parent for null-parented creations
0465: // first try finding the first form-compatible bean
0466: if (defaultParent == null) {
0467: Bean bean = firstBeanOfType(getBeans(), UIForm.class);
0468: if (bean instanceof MarkupBean)
0469: defaultParent = (MarkupBean) bean;
0470: }
0471:
0472: // if not found and this is a complete page, then we need to create a new default form bean
0473: if (defaultParent == null) {
0474: if (isPage()) {
0475: // Use the braveheart form component on Braveheart pages
0476: // if (DesignerServiceHack.getDefault().isBraveheartPage(pgunit.getSourceDom())) {
0477: if (InSyncServiceProvider.get().isWoodstockPage(
0478: pgunit.getSourceDom())) {
0479: defaultParent = (MarkupBean) addBean(
0480: getBeanInfo(com.sun.webui.jsf.component.Form.class
0481: .getName()), null, "form", null,
0482: null);
0483: } else if (InSyncServiceProvider.get()
0484: .isBraveheartPage(pgunit.getSourceDom())) {
0485: defaultParent = (MarkupBean) addBean(
0486: getBeanInfo(com.sun.rave.web.ui.component.Form.class
0487: .getName()), null, "form", null,
0488: null);
0489: } else {
0490: defaultParent = (MarkupBean) addBean(
0491: getBeanInfo("javax.faces.component.html.HtmlForm"),
0492: null, "form", null, null);
0493: }
0494: } else {
0495: Bean[] beans = getBeans();
0496: if (!isPage()) {
0497: Element root = document.getDocumentElement();
0498: Element subview = Util.findChild("f:subview", root,
0499: false);
0500: if (subview != null) {
0501: defaultParent = (MarkupBean) firstBeanOfType(
0502: beans, "f:subview");
0503: // This might be the wrong place to set it...
0504:
0505: // The id must begin with a Character.isLetter or _, and the rest of
0506: // the characters must be isLetter(), isDigit(), or _.
0507: // (Otherwise UIComponentBase.validateId() will barf at runtime)
0508: // Generating this is pretty easy for us because we already know
0509: // that the markup file must be named a valid Java classname,
0510: // and will therefore follow the right conventions! Thus, all we
0511: // need to do is strip out the extension and we're done!
0512: String id = pgunit.getFileObject().getName(); // no extension
0513: pgunit.ensureAttributeValue(subview, "id", id); // NOI18N
0514: }
0515: if (defaultParent == null) {
0516: defaultParent = firstBeanOfType(beans, root
0517: .getTagName());
0518: }
0519: } else {
0520: // then try <body>, <div> and finally an outer <html>
0521: defaultParent = (MarkupBean) firstBeanOfType(beans,
0522: HtmlTag.BODY.name);
0523: }
0524: if (defaultParent == null)
0525: defaultParent = firstBeanOfType(beans,
0526: HtmlTag.FRAMESET.name);
0527: if (defaultParent == null)
0528: defaultParent = firstBeanOfType(beans,
0529: HtmlTag.DIV.name);
0530: if (defaultParent == null)
0531: defaultParent = firstBeanOfType(beans,
0532: HtmlTag.HTML.name);
0533: // worst case, create an outer <div>
0534: if (defaultParent == null) {
0535: // defaultParent = (MarkupBean)addBean(getBeanInfo("org.netbeans.modules.visualweb.xhtml.Div"), null, "div", null, null);
0536: defaultParent = (MarkupBean) addBean(
0537: getBeanInfo(org.netbeans.modules.visualweb.xhtml.Div.class
0538: .getName()), null, "div", null,
0539: null);
0540: }
0541: }
0542: }
0543: }
0544:
0545: /*
0546: * We override as a no-op here since parenting for a FacesPageUnit is defined by the JSP XML elements hierarchy.
0547: * In a regular bean the parentage is flat.
0548: */
0549: protected void bindBeanParents() {
0550: }
0551:
0552: /**
0553: * Find the first occurance of a child bean with a given tag.
0554: *
0555: * @param bean the parent bean whose children will be searched.
0556: * @param tagName the tag name to search for.
0557: * @return the MarkupBean found, or null of none.
0558: */
0559: protected static MarkupBean firstBeanOfType(Bean bean,
0560: String tagName) {
0561: if (bean instanceof MarkupBean) {
0562: MarkupBean mbean = (MarkupBean) bean;
0563: Element element = mbean.getElement();
0564: if (element.getTagName().equals(tagName))
0565: return mbean;
0566: }
0567: Bean[] kids = bean.getChildren();
0568: return kids != null ? firstBeanOfType(kids, tagName) : null;
0569: }
0570:
0571: /**
0572: * Find the first occurance of a bean from a list, with a given tag.
0573: *
0574: * @param beans the list of beans to be searched.
0575: * @param tagName the tag name to search for.
0576: * @return the MarkupBean found, or null of none.
0577: */
0578: protected static MarkupBean firstBeanOfType(Bean[] beans,
0579: String tagName) {
0580: for (int i = 0; i < beans.length; i++) {
0581: MarkupBean kb = firstBeanOfType(beans[i], tagName);
0582: if (kb != null)
0583: return kb;
0584: }
0585: return null;
0586: }
0587:
0588: /*
0589: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#newBoundBean(java.beans.BeanInfo, java.lang.String, org.netbeans.modules.visualweb.insync.java.Field, org.netbeans.modules.visualweb.insync.java.Method, org.netbeans.modules.visualweb.insync.java.Method)
0590: */
0591: protected Bean newBoundBean(BeanInfo bi, String name,
0592: List<String> typeNames) {
0593: String tag = getBeanTagName(bi);
0594: // Determine the source tag for this bean and if not a faces bean to
0595: // bind, delegate it to super class
0596: if (tag == null) {
0597: return super .newBoundBean(bi, name, typeNames);
0598: }
0599: return null;
0600: }
0601:
0602: /**
0603: * We will create any unparented beans, or faces beans with faces parents
0604: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#canCreateBean(java.beans.BeanInfo, org.netbeans.modules.visualweb.insync.beans.Bean)
0605: */
0606: public boolean canCreateBean(BeanInfo bi, Bean parent) {
0607: boolean can = bi != null
0608: && (parent == null || parent instanceof MarkupBean
0609: && parent.isParentCapable()
0610: && (isFacesBean(bi) || isHtmlBean(bi)));
0611: assert Trace.trace("insync.faces", "FU.canCreateBean"
0612: + " type:"
0613: + bi.getBeanDescriptor().getBeanClass().getName()
0614: + " parent:" + parent + " can:" + can);
0615: return can;
0616: }
0617:
0618: /*
0619: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#newCreatedBean(java.beans.BeanInfo, org.netbeans.modules.visualweb.insync.beans.Bean, java.lang.String, java.lang.String)
0620: */
0621: protected Bean newCreatedBean(BeanInfo bi, Bean parent,
0622: String name, String facet, Position pos) {
0623: // Determine the source tag for this bean and thus if it is faces
0624: String tag = getBeanTagName(bi);
0625: assert Trace
0626: .trace("insync.faces", "FU.newCreatedBean "
0627: + " type:"
0628: + bi.getBeanDescriptor().getBeanClass()
0629: .getName() + " parent:" + parent
0630: + " name:" + name + " tag:" + tag);
0631:
0632: // If not a faces bean, return new regular bean
0633: if (tag == null)
0634: return super .newCreatedBean(bi,
0635: parent instanceof FacesBean ? null : parent, name,
0636: facet, pos);
0637:
0638: // if bean parent supplied, use the corresponding markup & DOM parent
0639: MarkupBean mparent = parent instanceof MarkupBean ? (MarkupBean) parent
0640: : null;
0641: Element eparent = mparent != null ? mparent.getElement() : null;
0642:
0643: // If the beaninfo tells us where to default the section, then use that element parent,
0644: // and faces parent if none was given, or if the given parent is not within the section.
0645: String section = getBeanMarkupSection(bi);
0646: if (section != null) {
0647: Element se = findMarkupSectionElement(section);
0648: if (se != null && !MarkupUnit.isDescendent(se, eparent)) {
0649: eparent = se;
0650: mparent = getMarkupBean(eparent);
0651: // Bug fix # 6471512 - Script tag shows in outline head1, but in jsp under form1
0652: // reset the position
0653: pos = new MarkupPosition(-1);
0654: }
0655: }
0656:
0657: // last chance, default all un-parented beans to the form
0658: if (mparent == null && defaultParent != null) {
0659: mparent = defaultParent;
0660: eparent = mparent.getElement();
0661: }
0662:
0663: // Insert new faces element into page dom
0664: String tlUri = getBeanTaglibUri(bi);
0665: String tlPre = getBeanTaglibPrefix(bi);
0666: Element element = addCompElement(eparent, tlUri, tlPre, tag,
0667: facet, pos);
0668:
0669: // Return new HtmlBean that represents the html element only
0670: Bean bean;
0671: if (HtmlBean.isHtmlBean(bi)) {
0672: HtmlBean hbean = new HtmlBean(this , bi, tag, mparent,
0673: element);
0674: bean = hbean;
0675: }
0676: // or, return new FacesBean that encorporates the element and the java side
0677: else {
0678: FacesBean fbean = new FacesBean(this , bi, name, mparent,
0679: element);
0680: //fbean.insertEntry(null); // this bean's Java source position doesn't matter
0681:
0682: //Do not insert the binding for Faces Beans to the java source
0683: //beansToAdd.add(fbean);
0684: // Also do not set the binding
0685: //fbean.setBindingProperties();
0686:
0687: bean = fbean;
0688: }
0689:
0690: // now add to main list and to parent by position
0691: beans.add(bean);
0692: if (mparent != null) // add this child to parent
0693: mparent.addChild(bean, pos);
0694:
0695: return bean;
0696: }
0697:
0698: /**
0699: * Find the JSF element matching a given markup section name.
0700: * For example, if the section is "head", we should return
0701: * <head>. However, it's not that simple - we have to return
0702: * the JSF element which -renders- <head>, which in a typical
0703: * Braveheart page will be <ui:head>.
0704: */
0705: private Element findMarkupSectionElement(String section) {
0706: assert section != null;
0707:
0708: // <removing set/getRoot from RaveDocument>
0709: // if (document instanceof RaveDocument) {
0710: // RaveDocument doc = (RaveDocument)document;
0711: // if (doc.getRoot() != doc.getDocumentElement()) {
0712: // RaveElement e = (RaveElement)MarkupUnit.getFirstDescendantElement(doc.getRoot(), section);
0713: // ====
0714: if (model instanceof FacesModel) {
0715: FacesModel facesModel = (FacesModel) model;
0716: DocumentFragment html = facesModel.getHtmlDomFragment();
0717: Element effectiveRoot = null;
0718: NodeList nl = html.getChildNodes();
0719: for (int i = 0, n = nl.getLength(); i < n; i++) {
0720: Node node = nl.item(i);
0721: if (node.getNodeType() == Node.ELEMENT_NODE) {
0722: effectiveRoot = (Element) node;
0723: break;
0724: }
0725: }
0726: if (effectiveRoot != document.getDocumentElement()) {
0727: // RaveElement e = (RaveElement)MarkupUnit.getFirstDescendantElement(effectiveRoot, section);
0728: Element e = MarkupUnit.getFirstDescendantElement(
0729: effectiveRoot, section);
0730: // <removing set/getRoot from RaveDocument>
0731: if (e != null) {
0732: // MarkupDesignBean bean = e.getDesignBean();
0733: MarkupDesignBean bean = MarkupUnit
0734: .getMarkupDesignBeanForElement(e);
0735: if (bean != null) {
0736: return bean.getElement();
0737: }
0738: }
0739: }
0740: }
0741:
0742: // Old way
0743: return MarkupUnit.getFirstDescendantElement(document
0744: .getDocumentElement(), section);
0745: }
0746:
0747: /**
0748: * Add the appropriate element in the correct position for a new component.
0749: *
0750: * @param eparent The potential element parent. May be null to use default.
0751: * @param taglibUri The taglib URI for the new element tag.
0752: * @param tagPrefix The taglib prefix for the new element tag.
0753: * @param tag The element tag name.
0754: * @param facet The facet element that should wrap the new element.
0755: * @param pos The Position or MarkupPosition that more presicely defines where the element
0756: * should be placed.
0757: * @return The newly created element.
0758: */
0759: private Element addCompElement(Element eparent, String taglibUri,
0760: String tagPrefix, String tag, String facet, Position pos) {
0761:
0762: MarkupPosition mpos = pos instanceof MarkupPosition ? (MarkupPosition) pos
0763: : null;
0764: Node before = mpos != null ? mpos.getBeforeSibling() : null;
0765:
0766: // if no parent was provided pick the best one: specified one, default if available, body if
0767: // found, otherwise root
0768:
0769: if (mpos != null) {
0770: if (mpos.getUnderParent() instanceof Element)
0771: eparent = (Element) mpos.getUnderParent();
0772: else if (before != null
0773: && before.getParentNode() != eparent)
0774: eparent = (Element) before.getParentNode();
0775: }
0776: if (eparent == null) {
0777: if (defaultParent != null) {
0778: eparent = defaultParent.getElement();
0779: } else {
0780: eparent = MarkupUnit.getFirstDescendantElement(document
0781: .getDocumentElement(), HtmlTag.BODY.name);
0782: // framesets can't contain anything other than frame or
0783: // other nested framesets, so don't worry about that here
0784: if (eparent == null)
0785: eparent = document.getDocumentElement(); // need something at least
0786: }
0787: }
0788:
0789: // if before-sibling is missing, figure out from parent+index
0790: if (before == null) {
0791: if (pos != null && pos.getIndex() >= 0)
0792: before = eparent.getChildNodes().item(pos.getIndex());
0793: }
0794:
0795: // if this component is a facet, then inject the intermediate <f:facet> tag
0796: if (facet != null) {
0797: eparent = pgunit.addElement(eparent, before, URI_JSF_CORE,
0798: null, "facet");
0799: eparent.setAttribute("name", facet);
0800: }
0801:
0802: return pgunit.addElement(eparent, before, taglibUri, tagPrefix,
0803: tag);
0804: }
0805:
0806: /*
0807: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#moveBean(org.netbeans.modules.visualweb.insync.beans.Bean, org.netbeans.modules.visualweb.insync.beans.Bean, com.sun.rave.designtime.Position)
0808: */
0809: public void moveBean(Bean bean, Bean newparent, Position pos) {
0810: // change the Bean parentage
0811: super .moveBean(bean, newparent, pos);
0812:
0813: // for markup beans, fixup the markup DOM element parentage
0814: if (bean instanceof MarkupBean
0815: && newparent instanceof MarkupBean) {
0816: MarkupPosition mpos = pos instanceof MarkupPosition ? (MarkupPosition) pos
0817: : null;
0818: Node before = mpos != null ? mpos.getBeforeSibling() : null;
0819:
0820: // start out presuming parent element is the parent bean's element, then see if mpos
0821: // has a better one
0822: Element eparent = ((MarkupBean) newparent).element;
0823: if (mpos != null) {
0824: if (mpos.getUnderParent() instanceof Element)
0825: eparent = (Element) mpos.getUnderParent();
0826: else if (before != null
0827: && before.getParentNode() != eparent)
0828: eparent = (Element) before.getParentNode();
0829: }
0830:
0831: // if before-sibling is missing, figure out from parent+index
0832: if (before == null && pos.getIndex() >= 0) {
0833: Bean[] children = newparent.getChildren();
0834: for (int i = 0; i < children.length; i++) {
0835: Bean childBean = children[i];
0836: if (childBean == bean && i < (children.length - 1)) {
0837: before = ((MarkupBean) children[i + 1]).element;
0838: }
0839: }
0840: }
0841:
0842: eparent.insertBefore(((MarkupBean) bean).element, before);
0843: }
0844: }
0845:
0846: /*
0847: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#newBoundProperty(org.netbeans.modules.visualweb.insync.java.Statement)
0848: */
0849: protected Property newBoundProperty(Statement stmt) {
0850: return MarkupProperty.newBoundInstance(this , stmt);
0851: }
0852:
0853: /*
0854: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#bindEventSets()
0855: */
0856: protected void bindEventSets(List<Statement> stmts) {
0857: // bind statement-based event wiring
0858: super .bindEventSets(stmts);
0859:
0860: // bind markup-based event wiring
0861: for (Iterator i = beans.iterator(); i.hasNext();) {
0862: Bean bean = (Bean) i.next();
0863: if (bean instanceof FacesBean)
0864: ((FacesBean) bean).bindEventSets();
0865: }
0866: }
0867:
0868: //------------------------------------------------------------------------------------ Accessors
0869:
0870: /**
0871: * Set the default language for this unit.
0872: *
0873: * @param defaultLanguage
0874: */
0875: public void setDefaultLanguage(String defaultLanguage) {
0876: this .defaultLanguage = defaultLanguage;
0877: }
0878:
0879: /**
0880: * Return the default language to be used in this page
0881: *
0882: * @return The default language that should be used for this page if not otherwise specified
0883: */
0884: public String getDefaultLanguage() {
0885: return defaultLanguage;
0886: }
0887:
0888: /**
0889: * Set the default (response) encoding for this unit.
0890: *
0891: * @param encoding The default response encoding.
0892: */
0893: public void setDefaultEncoding(String encoding) {
0894: this .defaultEncoding = MarkupUnit.getIanaEncoding(encoding);
0895: }
0896:
0897: /**
0898: * Get the effective response encoding for this unit.
0899: *
0900: * @return The current response encoding.
0901: */
0902: public String getEncoding() {
0903: if (isPage()) {
0904: Element root = document.getDocumentElement();
0905: Element jspd = MarkupUnit.ensureElement(root,
0906: "jsp:directive.page", null);
0907: String ct = jspd.getAttribute("contentType");
0908: if (ct != null) {
0909: int cs = ct.indexOf("charset=");
0910: if (cs > 0)
0911: return ct.substring(cs + 8);
0912: }
0913: }
0914: return null;
0915: }
0916:
0917: /**
0918: * Set the effective response encoding for this unit.
0919: *
0920: * @param encoding The response encoding, or null to use default.
0921: */
0922: public void setEncoding(String encoding) {
0923: if (encoding == null)
0924: encoding = defaultEncoding;
0925: else
0926: encoding = MarkupUnit.getIanaEncoding(encoding);
0927: if (isPage()) {
0928: Element root = document.getDocumentElement();
0929: Element jspd = MarkupUnit.ensureElement(root,
0930: "jsp:directive.page", null);
0931: pgunit
0932: .ensureAttributeExists(jspd, "pageEncoding",
0933: encoding); // don't whack src encoding
0934: pgunit.ensureAttributeValue(jspd, "contentType",
0935: "text/html;charset=" + encoding);
0936: }
0937: }
0938:
0939: /**
0940: * Set the default encoding for the jsp source
0941: *
0942: * @param encoding
0943: */
0944: public void setDefaultSrcEncoding(String encoding) {
0945: this .defaultSrcEncoding = encoding;
0946: }
0947:
0948: /**
0949: * Listen for JSP encoding changes from the editor so that our layer can mirror the changes.
0950: *
0951: * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
0952: */
0953: public void propertyChange(PropertyChangeEvent e) {
0954: if (e.getPropertyName().equals("encoding")
0955: && pgunit.getState().isModelAvailable()) {
0956: try {
0957: writeLock(null);
0958: setSrcEncoding(pgunit.getEncoding());
0959: } finally {
0960: writeUnlock(null);
0961: }
0962: }
0963: }
0964:
0965: /**
0966: * Set the page source encoding to follow a dobj encoding property change
0967: *
0968: * @param encoding
0969: */
0970: private void setSrcEncoding(String encoding) {
0971: if (encoding == null)
0972: encoding = defaultSrcEncoding;
0973: if (isPage()) {
0974: Element root = document.getDocumentElement();
0975: Element jspd = MarkupUnit.ensureElement(root,
0976: "jsp:directive.page", null);
0977: pgunit.ensureAttributeValue(jspd, "pageEncoding", encoding);
0978: }
0979: }
0980:
0981: /**
0982: * Override super's to make sure our viewRoot is active
0983: *
0984: * @see org.netbeans.modules.visualweb.insync.faces.FacesUnit#getFacesContext()
0985: */
0986: public FacesContext getFacesContext() {
0987: FacesContext facesContext = super .getFacesContext();
0988: facesContext.setViewRoot(viewRoot); // the view root for the component tree to be rendered
0989: return facesContext;
0990: }
0991:
0992: /**
0993: * Get the JSF page view root for this unit.
0994: *
0995: * @return The view root for this JSF page.
0996: */
0997: public UIViewRoot getViewRoot() {
0998: return viewRoot;
0999: }
1000:
1001: /**
1002: * Return the form bean associated with the unit.
1003: *
1004: * @return the form bean associated with the unit.
1005: */
1006: public MarkupBean getDefaultParent() {
1007: return defaultParent;
1008: }
1009:
1010: /**
1011: * Set the default parent associated with this unit.
1012: * The passed in bean must be a valid bean in this faces unit.
1013: * Avoid this method, this is primarily used by clients that
1014: * really know what they're doing and don't want any of the
1015: * default behavior - such as the Page Import feature.
1016: */
1017: public void setDefaultParent(MarkupBean defaultParent) {
1018: this .defaultParent = defaultParent;
1019: }
1020:
1021: /*
1022: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#getRootElement()
1023: */
1024: public org.w3c.dom.Element getRootElement() {
1025: return document.getDocumentElement();
1026: }
1027:
1028: /*
1029: * @see org.netbeans.modules.visualweb.insync.beans.BeansUnit#getRootInstance()
1030: */
1031: public Object getRootInstance() {
1032: return getViewRoot(); //container.getFacesContext().getViewRoot();
1033: }
1034:
1035: /**
1036: * Get the component binding string for a given component bean name.
1037: *
1038: * @param bname The component bean name.
1039: * @return The component binding string.
1040: */
1041: public final String getCompBinding(String bname) {
1042: String jbname = getBeanName();
1043: return "#{" + jbname + "." + bname + "}";
1044: }
1045:
1046: /**
1047: * @return
1048: */
1049: public MarkupUnit getPageUnit() {
1050: return pgunit;
1051: }
1052:
1053: /**
1054: * Return the child element with a given id attr within the page
1055: *
1056: * @param id The id attrribute string.
1057: * @return The matching component element if found.
1058: */
1059: public Element findCompElement(String id) {
1060: return MarkupUnit.getDescendantElementByAttr(document
1061: .getDocumentElement(), "*", FacesBean.ID_ATTR, id);
1062: }
1063:
1064: /**
1065: * Get the markup bean for a given faces tag element.
1066: *
1067: * @param e the tag element.
1068: * @return the markup bean for the given faces tag element, null if element has no associated
1069: * bean //!CQ TODO: could hash element=>bean instead
1070: */
1071: public MarkupBean getMarkupBean(Element e) {
1072: Bean[] beans = getBeans();
1073: for (int i = 0; i < beans.length; i++) {
1074: if (beans[i] instanceof MarkupBean) {
1075: MarkupBean mb = (MarkupBean) beans[i];
1076: if (mb.element == e) {
1077: assert Trace.trace("insync.faces",
1078: "FU.getMarkupBean:" + e + " => " + mb);
1079: return mb;
1080: }
1081: }
1082: }
1083: assert Trace.trace("insync.faces", "FU.getMarkupBean:" + e
1084: + " => null");
1085: return null;
1086: }
1087:
1088: /**
1089: * Get the faces bean for a given faces tag element.
1090: *
1091: * @param e the tag element.
1092: * @return the faces bean for the given faces tag element, null if not a faces element.
1093: */
1094: public FacesBean getFacesBean(Element e) {
1095: MarkupBean mb = getMarkupBean(e);
1096: return mb instanceof FacesBean ? (FacesBean) mb : null;
1097: }
1098:
1099: /**
1100: * @return the faces bean for a given faces tag element or any of its ancestors. Null if not a
1101: * faces element.
1102: */
1103: public FacesBean getFacesAncestorBean(Element e) {
1104: Element p = e;
1105: do {
1106: FacesBean fbean = getFacesBean(p);
1107: if (fbean != null)
1108: return fbean;
1109: p = (Element) p.getParentNode();
1110: } while (p != null);
1111: return null;
1112: }
1113:
1114: /**
1115: * Debug method for dumping a faces tree to stderr
1116: *
1117: * @param uic The parent of the tree to dump.
1118: * @param indent The indent level, 0 to start.
1119: */
1120: public void dumpFacesComp(UIComponent uic, int indent) {
1121: for (int i = 0; i < indent; i++)
1122: System.err.print(" ");
1123: System.err.println(uic);
1124: List kids = uic.getChildren();
1125: for (Iterator i = kids.iterator(); i.hasNext();) {
1126: UIComponent kid = (UIComponent) i.next();
1127: dumpFacesComp(kid, indent + 1);
1128: }
1129: }
1130:
1131: /**
1132: * Get a complete render tree as a document fragment for a given component bean.
1133: *
1134: * @param lbean The bean to render in DesignBean form.
1135: * @param lu The LiveUnit that hosts the given bean.
1136: * @return A complete DocumentFragment that contains the bean's rendered XHTML.
1137: * //!TODO: could easily extract lu from lbean here.
1138: */
1139: public DocumentFragment getFacesRenderTree(DesignBean lbean,
1140: LiveUnit lu) {
1141: assert Trace.trace("insync.faces",
1142: "FU.getFacesRenderTree bean:" + lbean);
1143:
1144: renderFailure = null;
1145: renderFailureComponent = null;
1146:
1147: // If we're looking for the pre-rendered fragment we're done
1148: if (preRendered == lbean) {
1149: // We need to make a copy since clients are allowed to (and do) mutate the
1150: // returned DocumentFragment from this method.
1151: // DocumentFragment df = (DocumentFragment)getPageUnit().getSourceDom().importNode(preRenderedFragment, true);
1152: // return df;
1153: return (DocumentFragment) getPageUnit().getRenderedDom()
1154: .importNode(preRenderedFragment, true);
1155: }
1156:
1157: // create an empty doc fragment, & then render into it.
1158: // DocumentFragment df = getPageUnit().getSourceDom().createDocumentFragment();
1159: DocumentFragment df = getPageUnit().getRenderedDom()
1160: .createDocumentFragment();
1161:
1162: ClassLoader oldContextClassLoader = Thread.currentThread()
1163: .getContextClassLoader();
1164: try {
1165: Thread.currentThread().setContextClassLoader(
1166: getClassLoader());
1167: // DocFragmentJspWriter rw = container.beginRender(lu, viewRoot, df);
1168: DocFragmentJspWriter rw = new DocFragmentJspWriter(
1169: container, df);
1170: container.beginRender(lu, viewRoot, rw);
1171:
1172: if (preRendered != null) {
1173: rw.setPreRendered((UIComponent) preRendered
1174: .getInstance(), preRenderedFragment);
1175: }
1176:
1177: try {
1178: if (getFacesBean(lbean) == null) {
1179: Element element;
1180: if (lbean instanceof MarkupDesignBean) {
1181: element = ((MarkupDesignBean) lbean)
1182: .getElement();
1183: } else {
1184: element = null;
1185: }
1186:
1187: HashMap map = new HashMap();
1188: addFirstFacesBeans(lbean, map);
1189: renderNode(element, map, rw, lu);
1190: } else {
1191: renderBean(lbean, rw, lu);
1192: }
1193: } catch (RenderError re) {
1194: // We've already handled these at the throw point
1195: ;
1196: }
1197: container.endRender(rw);
1198:
1199: //System.err.println("rendered:" + b + " as:");
1200: //getPageUnit().dump(df, new java.io.PrintWriter(System.err, true), 0);
1201:
1202: //System.err.println("ViewRoot");
1203: //dumpFacesComp(viewRoot, 1);
1204:
1205: return df;
1206: } finally {
1207: Thread.currentThread().setContextClassLoader(
1208: oldContextClassLoader);
1209: }
1210: }
1211:
1212: /**
1213: * Search down the hierarchy from the given design bean and add the
1214: * first (element, DesignBean) pair found in each bean subtree that corresponds
1215: * to a FacesBean.
1216: */
1217: private void addFirstFacesBeans(DesignBean curr, HashMap map) {
1218: FacesBean fb = getFacesBean(curr);
1219: if (fb != null) {
1220: map.put(((MarkupDesignBean) curr).getElement(), curr);
1221: // Stop searching this subtree
1222: return;
1223: }
1224: for (int i = 0, n = curr.getChildBeanCount(); i < n; i++) {
1225: addFirstFacesBeans(curr.getChildBean(i), map);
1226: }
1227: }
1228:
1229: /**
1230: * Set the "pre rendered" DocumentFragment for a particular bean.
1231: * Note: Only ONE bean can be pre-rendered at a time; this is not
1232: * a per-bean assignment. When set, this will cause the given
1233: * DocumentFragment to be inserted into the output fragment
1234: * rather than calling the bean's renderer.
1235: *
1236: * The bean must represent a UIComponent.
1237: *
1238: * This is intended to be used for for example having the ability
1239: * to "inline edit" a particular component's value; in that case
1240: * since we're not updating the value attribute during editing,
1241: * we want to suppress the normal rendered portion from the component
1242: * and instead substitute the inline-edited document fragment
1243: * corresponding to the parsed text output of the component.
1244: */
1245: public void setPreRendered(DesignBean bean, DocumentFragment df) {
1246: preRendered = bean;
1247: assert bean == null
1248: || bean.getInstance() instanceof UIComponent;
1249: preRenderedFragment = df;
1250: }
1251:
1252: /**
1253: * Return the exception associated with the most recent getFacesRenderTree call.
1254: * You should call this method immediately after a {@link #getFacesRenderTree} call;
1255: * it will be overwritten by other requests. If there were no failures, returns null. */
1256: public Exception getRenderFailure() {
1257: return renderFailure;
1258: }
1259:
1260: /**
1261: * Return the failing component associated with the most recent getFacesRenderTree call.
1262: * You should call this method immediately after a {@link #getFacesRenderTree} call;
1263: * it will be overwritten by other requests. If there were no failures, returns null. */
1264: public DesignBean getRenderFailureComponent() {
1265: return renderFailureComponent;
1266: }
1267:
1268: /**
1269: * Called when component rendering aborts with an exception. Inserts a representative
1270: * icon and scrapes away any output already produced by the component.
1271: */
1272: private void renderError(Exception e, DesignBean bean,
1273: DocFragmentJspWriter rw, Node currentPos) {
1274: renderFailure = e;
1275: renderFailureComponent = bean;
1276:
1277: UIComponent uic = (UIComponent) bean.getInstance();
1278:
1279: // Log as "informational" to suppress footer messages and such
1280: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
1281:
1282: Node parent = rw.getCurrent();
1283:
1284: if (currentPos != null) {
1285: parent = currentPos;
1286: }
1287:
1288: if (parent != null) {
1289: // Nuke everything related to this component
1290: ArrayList nodes = new ArrayList();
1291: Node curr = parent.getFirstChild();
1292:
1293: while (curr != null) {
1294: // if (curr instanceof RaveElement && (((RaveElement)curr).getDesignBean() == bean)) {
1295: if (curr instanceof Element
1296: && MarkupUnit
1297: .getMarkupDesignBeanForElement((Element) curr) == bean) {
1298: nodes.add(curr);
1299: }
1300:
1301: curr = curr.getNextSibling();
1302: }
1303:
1304: Iterator it = nodes.iterator();
1305:
1306: while (it.hasNext()) {
1307: Node n = (Node) it.next();
1308: parent.removeChild(n);
1309: }
1310: }
1311:
1312: Document document = parent.getOwnerDocument();
1313: Element span = document.createElement(HtmlTag.SPAN.name);
1314: parent.appendChild(span); // TODO -- insert before instead?
1315:
1316: String style = "";
1317: DesignProperty property = bean.getProperty("style"); // NOI18N
1318:
1319: if (property != null) {
1320: Object o = property.getValueSource();
1321:
1322: if (o instanceof String) {
1323: style = (String) o;
1324: }
1325: }
1326:
1327: if (style.length() > 0) {
1328: span.setAttribute(HtmlAttribute.STYLE, style); // NOI18N
1329: }
1330:
1331: Element img = document.createElement(HtmlTag.IMG.name);
1332: span.appendChild(img);
1333:
1334: String url = org.netbeans.modules.visualweb.insync.SourceUnit.class
1335: .getResource("error-glyph.gif").toExternalForm();
1336: img.setAttribute(HtmlAttribute.SRC, url);
1337: String text = bean.getInstanceName();
1338: String msg = e.getLocalizedMessage();
1339: if (msg != null && msg.length() > 0) {
1340: // Put the tag name inside a <b>
1341: Element b = document.createElement(HtmlTag.B.name);
1342: span.appendChild(b);
1343: b.appendChild(document.createTextNode(text));
1344: span.appendChild(document.createTextNode(": " + msg));
1345: } else {
1346: span.appendChild(document.createTextNode(text));
1347: }
1348:
1349: if (uic != null) {
1350: // ((RaveElement)span).setDesignBean((MarkupDesignBean)bean);
1351: // ((RaveElement)img).setDesignBean((MarkupDesignBean)bean);
1352: MarkupUnit.setMarkupDesignBeanForElement(span,
1353: (MarkupDesignBean) bean);
1354: MarkupUnit.setMarkupDesignBeanForElement(img,
1355: (MarkupDesignBean) bean);
1356: }
1357:
1358: throw new RenderError(e);
1359: }
1360:
1361: /**
1362: * Render a component into a doc fragment writer
1363: *
1364: * @param lbean The bean to render in DesignBean form.
1365: * @param rw The JSP response writer to write the rendering into.
1366: * @param lu The LiveUnit that hosts the given bean.
1367: */
1368: protected void renderBean(DesignBean lbean,
1369: DocFragmentJspWriter rw, LiveUnit lu) {
1370: FacesContext facesContext = container.getFacesContext();
1371: UIComponent uic = (UIComponent) lbean.getInstance();
1372:
1373: Node currentPos = rw.getCurrent();
1374: int depth = rw.getDepth();
1375: int startIndex = currentPos != null ? currentPos
1376: .getChildNodes().getLength() : -1;
1377: try {
1378: try {
1379: Trace
1380: .trace("insync.faces",
1381: "renderView encodeBegin...");
1382: uic.encodeBegin(facesContext);
1383: } catch (Exception e) {
1384: Trace.trace("jsfsupport.container",
1385: "error in tag encode: resetting");
1386: renderError(e, lbean, rw, currentPos);
1387: // Abort tag and its children entirely
1388: return;
1389: }
1390:
1391: //int childrenStart = rw.getPosition();
1392: try {
1393: Trace.trace("insync.faces",
1394: "renderView encodeChildren...");
1395:
1396: // If the component renders its own children, then let it take care of them
1397: if (uic.getRendersChildren()) {
1398: uic.encodeChildren(facesContext);
1399: }
1400: // Otherwise, we need to render the children like a JSP page does, including the markup
1401: else {
1402: // prepare a map of elements => facesbeans
1403: HashMap bkidmap = new HashMap();
1404: for (int i = lbean.getChildBeanCount() - 1; i >= 0; i--) {
1405: DesignBean lbkid = lbean.getChildBean(i);
1406: FacesBean bkid = getFacesBean(lbkid);
1407: if (bkid != null) {
1408: bkidmap.put(bkid.getElement(), lbkid);
1409: } else {
1410: addFirstFacesBeans(lbkid, bkidmap);
1411: }
1412: }
1413: Element e = getFacesBean(lbean).getElement();
1414: NodeList ekids = e.getChildNodes();
1415: int ekidcount = ekids.getLength();
1416:
1417: for (int ei = 0; ei < ekidcount; ei++) {
1418: Node child = ekids.item(ei);
1419:
1420: // Facets should not be designtime rendered - component
1421: // parents should handle that themselves (we don't know where
1422: // they go!)
1423: if (child.getNodeType() == Node.ELEMENT_NODE) {
1424: Element element = (Element) child;
1425: if (element.getLocalName().equals("facet")
1426: && // NOI18N
1427: element.getNamespaceURI().equals(
1428: URI_JSF_CORE)) {
1429: continue;
1430: }
1431: }
1432:
1433: renderNode(child, bkidmap, rw, lu);
1434: }
1435: }
1436: } catch (Exception e) {
1437: Trace.trace("insync.faces",
1438: "error in encodeChildren: resetting");
1439:
1440: renderError(e, lbean, rw, currentPos);
1441: return; // abort tag
1442: }
1443:
1444: try {
1445: Trace.trace("insync.faces", "renderView encodeEnd...");
1446: uic.encodeEnd(facesContext);
1447: } catch (Exception e) {
1448: Trace.trace("insync.faces",
1449: "error in encodeEnd: resetting");
1450: renderError(e, lbean, rw, currentPos);
1451: }
1452: } finally {
1453: if (currentPos != null) {
1454: NodeList nl = currentPos.getChildNodes();
1455: int endIndex = nl.getLength();
1456: DocumentFragment df = rw.getFragment();
1457: annotateRender(lbean, df, currentPos, startIndex,
1458: endIndex);
1459: if (uic.getRendersChildren()) {
1460: // The component may have rendered other JSF components as its children.
1461: // We should try to annotate these too. We can't know accurately
1462: // which nodes they emitted (and if they didn't emit anything, we have
1463: // no way to know where they would have gone, so we can't call
1464: // annotateRender in that case.)
1465: Node curr = nl.item(startIndex);
1466: int index = startIndex;
1467: while (curr != null) {
1468: // if (curr instanceof RaveElement) {
1469: // annotateRenderTree(lbean, df, (RaveElement)curr, index);
1470: // }
1471: if (curr instanceof Element) {
1472: annotateRenderTree(lbean, df,
1473: (Element) curr, index);
1474: }
1475: index++;
1476: curr = curr.getNextSibling();
1477: }
1478: }
1479: }
1480:
1481: // Ensure that children aborting during encode doesn't
1482: // leave the current node pointing somewhere in the subtree
1483: rw.setCurrent(currentPos, depth);
1484: }
1485: }
1486:
1487: /**
1488: * Return the FacesBean for the live bean. May be null, for non faces live beans.
1489: *
1490: * @param lb The live bean to get the faces bean for. May be null.
1491: * @return the FacesBean corresponding to the live bean, or null.
1492: */
1493: public static FacesBean getFacesBean(DesignBean lb) {
1494: if (!(lb instanceof BeansDesignBean)) {
1495: return null;
1496: }
1497: Bean b = ((BeansDesignBean) lb).getBean();
1498: if (b instanceof FacesBean) {
1499: return (FacesBean) b;
1500: }
1501: return null;
1502: }
1503:
1504: /**
1505: * Call annotateRender on a constructed component (if it has an annotateRender)
1506: *
1507: * @param bean
1508: * @param df
1509: * @param parent
1510: * @param start
1511: * @param end
1512: */
1513: private void annotateRender(DesignBean bean, DocumentFragment df,
1514: Node parent, int start, int end) {
1515: DesignInfo lbi = bean.getDesignInfo();
1516: if (lbi instanceof MarkupDesignInfo
1517: && bean instanceof MarkupDesignBean) {
1518: MarkupDesignInfo mlbi = (MarkupDesignInfo) lbi;
1519: int length = parent.getChildNodes().getLength();
1520: Node startBefore = start >= 0 && start < length ? parent
1521: .getChildNodes().item(start) : null;
1522: Node endBefore = end >= 0 && end < length ? parent
1523: .getChildNodes().item(end) : null;
1524: MarkupPosition startPos = new MarkupPosition(parent,
1525: startBefore);
1526: MarkupPosition endPos = new MarkupPosition(parent,
1527: endBefore);
1528: renderContext.fragment = df;
1529: renderContext.begin = startPos;
1530: renderContext.end = endPos;
1531: mlbi
1532: .customizeRender((MarkupDesignBean) bean,
1533: renderContext);
1534: }
1535: }
1536:
1537: private RenderContext renderContext = new RenderContext();
1538:
1539: /**
1540: *
1541: */
1542: private class RenderContext implements MarkupRenderContext {
1543: private DocumentFragment fragment;
1544: private MarkupPosition begin;
1545: private MarkupPosition end;
1546:
1547: public DocumentFragment getDocumentFragment() {
1548: return fragment;
1549: }
1550:
1551: public MarkupPosition getBeginPosition() {
1552: return begin;
1553: }
1554:
1555: public MarkupPosition getEndPosition() {
1556: return end;
1557: }
1558:
1559: public void associateMouseRegion(Element element,
1560: MarkupMouseRegion region) {
1561: // if (element instanceof RaveElement) {
1562: // ((RaveElement)element).setMarkupMouseRegion(region);
1563: // }
1564: setMarkupMouseRegionForElement(element, region);
1565: }
1566: }
1567:
1568: // private static final Map element2region = new WeakHashMap(200);
1569: // private static final String KEY_MARKUP_MOUSE_REGION = "vwpMarkupMouseRegion"; // NOI18N
1570:
1571: private static final Map<Element, MarkupMouseRegion> element2region = new WeakHashMap<Element, MarkupMouseRegion>(
1572: 200);
1573:
1574: public static void setMarkupMouseRegionForElement(Element element,
1575: MarkupMouseRegion region) {
1576: // synchronized (element2region) {
1577: // element2region.put(element, region);
1578: // }
1579: if (element == null) {
1580: return;
1581: }
1582: // element.setUserData(KEY_MARKUP_MOUSE_REGION, region, MarkupMouseRegionDataHandler.getDefault());
1583: element2region.put(element, region);
1584: }
1585:
1586: public static MarkupMouseRegion getMarkupMouseRegionForElement(
1587: Element element) {
1588: // synchronized (element2region) {
1589: // return (MarkupMouseRegion)element2region.get(element);
1590: // }
1591: if (element == null) {
1592: return null;
1593: }
1594: // return (MarkupMouseRegion)element.getUserData(KEY_MARKUP_MOUSE_REGION);
1595: return element2region.get(element);
1596: }
1597:
1598: // private static class MarkupMouseRegionDataHandler implements UserDataHandler {
1599: // private static final MarkupMouseRegionDataHandler INSTANCE = new MarkupMouseRegionDataHandler();
1600: //
1601: // public static MarkupMouseRegionDataHandler getDefault() {
1602: // return INSTANCE;
1603: // }
1604: //
1605: // public void handle(short operation, String key, Object data, Node src, Node dst) {
1606: // }
1607: // } // End of MarkupMouseRegionDataHandler.
1608:
1609: /**
1610: * Recursively annotateRender on any components found in the node tree (not counting the node
1611: * itself); e.g. only process its children.
1612: */
1613: private void annotateRenderTree(DesignBean parentBean,
1614: DocumentFragment df, Element element, int index) {
1615: // DesignBean lb = element.getDesignBean();
1616: DesignBean lb = MarkupUnit
1617: .getMarkupDesignBeanForElement(element);
1618:
1619: if (lb != null) {
1620: // Do it bottom up
1621: NodeList nl = element.getChildNodes();
1622: int n = nl.getLength();
1623: for (int i = 0; i < n; i++) {
1624: Node child = nl.item(i);
1625: // if (child instanceof RaveElement) {
1626: // RaveElement ce = (RaveElement)child;
1627: // annotateRenderTree(lb, df, ce, i);
1628: // }
1629: if (child instanceof Element) {
1630: annotateRenderTree(lb, df, (Element) child, i);
1631: }
1632: }
1633: }
1634:
1635: if (lb != null && lb != parentBean) {
1636: annotateRender(lb, df, element.getParentNode(), index,
1637: index + 1);
1638: }
1639: }
1640:
1641: /**
1642: * Write a source node's view representation into a doc fragment writer
1643: * @param node
1644: * @param beanmap
1645: * @param rw
1646: * @param lu
1647: */
1648: private void renderNode(Node node, Map beanmap,
1649: DocFragmentJspWriter rw, LiveUnit lu) {
1650: switch (node.getNodeType()) {
1651: case Node.ATTRIBUTE_NODE:
1652: return; // ignore
1653:
1654: case Node.ELEMENT_NODE:
1655: // if the node is an element that belongs to a facesbean, then render the bean
1656: DesignBean bkid = (DesignBean) beanmap.get(node);
1657: if (bkid != null) {
1658: try {
1659: renderBean(bkid, rw, lu);
1660: } catch (Exception e) {
1661: Trace.trace("insync.faces",
1662: "error in child encode: resetting");
1663: UIComponent kid = (UIComponent) bkid.getInstance();
1664: renderError(e, bkid, rw, null);
1665: }
1666: beanmap.remove(node);
1667: return;
1668: }
1669:
1670: // recurse when replicating elements in case we have a child burried under one
1671: rw.importNode(node, false);
1672: for (Node nkid = node.getFirstChild(); nkid != null; nkid = nkid
1673: .getNextSibling())
1674: renderNode(nkid, beanmap, rw, lu);
1675: rw.popNode();
1676: return;
1677:
1678: // Text nodes need to be "rendered" from JSPX to HTML.
1679: // For example, let's say the JSPX is this: "Hello&nbsp;World" -
1680: // e.g. "Hello World" where the space is a nonbreaking space.
1681: // The XML parser will already have processed the & so the
1682: // text node we're given for this is "Hello World". Since
1683: // we're creating a text node directly we need to process the entities
1684: // ourselves (otherwise the text node would say "Hell World"
1685: // literally). The utility method Entities.expand will find
1686: // and replace any of the 253 HTML entities in a string with their
1687: // corresponding characters, but only do this if we know that the
1688: // string contains an entity (e.g. there is a & in the string).
1689: case Node.TEXT_NODE: {
1690: String s = node.getNodeValue();
1691: // if (((RaveRenderNode)node).isJspx() && (s.indexOf('&') != -1)) {
1692: if (MarkupService.isJspxNode(node)
1693: && (s.indexOf('&') != -1)) {
1694: // <markup_separation>
1695: // s = MarkupServiceProvider.getDefault().expandHtmlEntities(s, false);
1696: // ====
1697: s = Entities.expandHtmlEntities(s, false);
1698: // </markup_separation>
1699: }
1700: Node newnode = rw.appendTextNode(s);
1701: // Think of a better name than "jsp" and "html" here; I'm
1702: // realy dealing with rendered vs source
1703: if (newnode != null) {
1704: // ((RaveText)newnode).setSource((RaveText)node);
1705: MarkupService.setSourceTextForText((Text) newnode,
1706: (Text) node);
1707: }
1708: return;
1709: }
1710:
1711: // other text node type can be simply deep copied
1712: case Node.CDATA_SECTION_NODE:
1713: case Node.ENTITY_REFERENCE_NODE:
1714: case Node.COMMENT_NODE: // because of <script> nodes and <style> nodes
1715: Node newnode = rw.importNode(node, true);
1716: return;
1717: }
1718: }
1719:
1720: //--------------------------------------------------------------------------------------- Object
1721:
1722: /*
1723: * @see java.lang.Object#toString()
1724: */
1725: public String toString() {
1726: StringBuffer sb = new StringBuffer(30);
1727: sb.append("[FacesPageUnit pkg:" + getThisPackageName()
1728: + " cls:" + getThisClassName() + " name:"
1729: + junit.getName());
1730: sb.append("]");
1731: return sb.toString();
1732: }
1733:
1734: public class RenderError extends Error {
1735: private RenderError(Exception cause) {
1736: super (cause);
1737: }
1738: }
1739:
1740: // <copied from designer/FacesSupport>
1741: public static DocumentFragment renderHtml(FacesModel model,
1742: MarkupDesignBean bean) {
1743: return renderHtml(model, bean, true);
1744: }
1745:
1746: public static DocumentFragment renderHtml(FacesModel model,
1747: MarkupDesignBean bean, boolean markRendered) {
1748: ClassLoader oldContextClassLoader = Thread.currentThread()
1749: .getContextClassLoader();
1750: try {
1751: FacesModelSet facesModelSet = model.getFacesModelSet();
1752: if (facesModelSet == null) {
1753: // XXX Possible NPE, after the model was invalidated.
1754: return null;
1755: }
1756: Thread.currentThread().setContextClassLoader(
1757: model.getFacesModelSet().getProjectClassLoader());
1758: if (bean == null) {
1759: // First discover where to start rendering...
1760: // Look from the topmost render
1761: DesignBean r = model.getRootBean();
1762:
1763: if (r == null) {
1764: return null;
1765: }
1766:
1767: // XXX This isn't right. What if the user adds a property to the page
1768: // of type UIComponent? I've gotta get an insync API to get -the- view
1769: // hierarchy. Should I just go looking for f:view ?
1770: for (int i = 0, n = r.getChildBeanCount(); i < n; i++) {
1771: DesignBean b = (DesignBean) r.getChildBean(i);
1772:
1773: if (b instanceof MarkupDesignBean) {
1774: bean = (MarkupDesignBean) b;
1775:
1776: // Look for a bean that has a deep hierarchy - so we skip
1777: // things like f:loadBundle and such in case they occur
1778: // at the top level below f:view
1779: if ((bean.getChildBeanCount() > 0)
1780: && (bean.getChildBean(0)
1781: .getChildBeanCount() > 0)) {
1782: break; // has a grandchild - probably the main parent we're looking for.
1783: }
1784: }
1785: }
1786:
1787: if (bean == null) {
1788: return null;
1789: }
1790: }
1791:
1792: FacesPageUnit facesunit = model.getFacesUnit();
1793:
1794: DocumentFragment df = facesunit.getFacesRenderTree(bean,
1795: model.getLiveUnit());
1796:
1797: if (DEBUG) {
1798: debugLog("Rendered bean=" + bean + "\n"
1799: + InSyncServiceProvider.get().getHtmlStream(df)); // NOI18N
1800: }
1801:
1802: // // TODO: Rather than check for the box persistence side-effect flag, should I
1803: // // be smarter here and only mark rendered nodes if the target is the DomSynchronizer's
1804: // // DOM?
1805: if (markRendered) {
1806: // markRenderedNodes(null, df);
1807: MarkupService.markRenderedNodes(df);
1808: }
1809:
1810: return df;
1811: } finally {
1812: Thread.currentThread().setContextClassLoader(
1813: oldContextClassLoader);
1814: }
1815: }
1816:
1817: /** Debugging flag. */
1818: private static final boolean DEBUG = ErrorManager.getDefault()
1819: .getInstance(FacesPageUnit.class.getName()).isLoggable(
1820: ErrorManager.INFORMATIONAL);
1821:
1822: /** Logs debug message. Use only after checking <code>DEBUG</code> flag. */
1823: private static void debugLog(String message) {
1824: ErrorManager.getDefault().getInstance(
1825: FacesPageUnit.class.getName()).log(message);
1826: }
1827:
1828: // /** Mark all nodes in a node tree as rendered HTML nodes, and point back to the
1829: // * source nodes in the JSP DOM. For nodes that all point to the same source
1830: // * node I want only the topmost nodes to point to the source.
1831: // */
1832: // private static void markRenderedNodes(Element parent, Node node) {
1833: // RaveElement element;
1834: // if (node instanceof RaveElement) {
1835: // element = (RaveElement)node;
1836: // } else {
1837: // element = null;
1838: // }
1839: //
1840: // // We work our way right to left, bottom to top, to ensure that
1841: // // the last setJsp call made for a particular jsp node will be the
1842: // // leftmost, topmost rendered node for that jsp element.
1843: // NodeList nl = node.getChildNodes();
1844: //
1845: // for (int n = nl.getLength(), i = n - 1; i >= 0; i--) {
1846: // markRenderedNodes(element, nl.item(i));
1847: // }
1848: //
1849: // if (node instanceof RenderNode) {
1850: // RenderNode rn = (RenderNode)node;
1851: //
1852: // if (element != null) {
1853: //// if ((parent != null) && (parent.getDesignBean() == element.getDesignBean())) {
1854: // if (parent != null
1855: // && MarkupUnit.getMarkupDesignBeanForElement(parent) == MarkupUnit.getMarkupDesignBeanForElement(element)) {
1856: // element.setSource(null);
1857: //// } else if (element.getDesignBean() != null) {
1858: //// element.setSource((RaveElement)element.getDesignBean().getElement());
1859: // } else if (MarkupUnit.getMarkupDesignBeanForElement(element) != null) {
1860: // element.setSource((RaveElement)MarkupUnit.getMarkupDesignBeanForElement(element).getElement());
1861: // } else {
1862: // rn.markRendered();
1863: // }
1864: // } else {
1865: // rn.markRendered();
1866: // }
1867: // }
1868: // }
1869: // </copied from designer/FacesSupport>
1870:
1871: }
|