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 com.sun.rave.web.ui.component;
0042:
0043: import java.io.Serializable;
0044: import java.util.List;
0045: import java.util.ArrayList;
0046: import java.util.Iterator;
0047: import java.util.StringTokenizer;
0048: import javax.faces.component.EditableValueHolder;
0049: import javax.faces.component.NamingContainer;
0050: import javax.faces.component.UIComponent;
0051: import javax.faces.component.UIData;
0052: import javax.faces.context.FacesContext;
0053: import javax.faces.event.FacesEvent;
0054: import javax.faces.event.ActionEvent;
0055: import com.sun.data.provider.RowKey;
0056: import java.util.Map;
0057: import java.util.HashMap;
0058: import java.util.Collections;
0059: import java.util.Set;
0060: import java.util.HashSet;
0061: import java.util.Collection;
0062: import java.util.Arrays;
0063: import com.sun.rave.web.ui.util.MessageUtil;
0064:
0065: /**
0066: * <p>Component that represents an input form.</p>
0067: */
0068:
0069: public class Form extends FormBase {
0070: private VirtualFormDescriptor submittedVirtualForm; //the virtual form that was submitted
0071: private static final String VF_DELIM_1 = ","; //NOI18N
0072: private static final String VF_DELIM_2 = "|"; //NOI18N
0073: private static final String ID_SEP = String
0074: .valueOf(NamingContainer.SEPARATOR_CHAR);
0075: public static final char ID_WILD_CHAR = '*';
0076: private static final String ID_WILD = String.valueOf(ID_WILD_CHAR);
0077: private transient Map erasedMap = new HashMap(); //has an EditableValueHolder as the key, and an Object[] value pair or a TableValues as the value
0078: private transient Set nonDefaultRetainStatusEvhs = new HashSet(); //contains EditableValueHolders with a retain status different from the default
0079: private static final boolean DEFAULT_RETAIN_STATUS = true; //default for whether non-participating submitted values are retained
0080:
0081: /**
0082: * <p>Override <code>UIForm.processDecodes(FacesContext)</code> to ensure
0083: * correct virtual form processing.</p>
0084: *
0085: * @param context <code>FacesContext</code> for the current request
0086: *
0087: * @exception NullPointerException Thrown when <code>context</code> is null
0088: */
0089: public void processDecodes(FacesContext context) {
0090: if (context == null) {
0091: throw new NullPointerException();
0092: }
0093:
0094: submittedVirtualForm = null;
0095: erasedMap.clear();
0096: //clearing out nonDefaultRetainStatusEvhs occurs in restoreNonParticipatingSubmittedValues
0097: //(which is called during renderering)
0098: //so that application code can muck with the retain statuses in preprocess
0099:
0100: // Process this component itself
0101: decode(context); //may set submittedVirtualForm
0102:
0103: // if we're not the submitted form, don't process children.
0104: if (!isSubmitted()) {
0105: return;
0106: }
0107:
0108: // Process all facets and children of this component
0109: Iterator kids = getFacetsAndChildren();
0110: while (kids.hasNext()) {
0111: UIComponent kid = (UIComponent) kids.next();
0112: kid.processDecodes(context);
0113: }
0114:
0115: if (submittedVirtualForm != null) {
0116: //if the children of the Form are known to participate in submittedVirtualForm,
0117: //then don't bother erasing
0118: if (!childrenAreKnownToParticipate(this ,
0119: submittedVirtualForm)) {
0120: eraseVirtualFormNonParticipants(this , null, null);
0121: }
0122: }
0123: }
0124:
0125: public void queueEvent(FacesEvent event) {
0126: FacesEvent relevantEvent = event;
0127: if (event instanceof WrapperEvent) {
0128: WrapperEvent wrapperEvent = (WrapperEvent) event;
0129: relevantEvent = wrapperEvent.getFacesEvent();
0130: }
0131: if (relevantEvent instanceof ActionEvent
0132: && submittedVirtualForm == null) {
0133: UIComponent sourceComp = relevantEvent.getComponent();
0134: VirtualFormDescriptor virtualFormComponentSubmits = getVirtualFormComponentSubmits(sourceComp);
0135: if (virtualFormComponentSubmits != null) { //if the source component does in fact submit a virtual form
0136: submittedVirtualForm = virtualFormComponentSubmits; //then set that virtual form as having been submitted
0137: }
0138: }
0139: super .queueEvent(event);
0140: }
0141:
0142: public void setSubmittedVirtualForm(VirtualFormDescriptor vfd) {
0143: if (submittedVirtualForm == null && vfd != null) {
0144: submittedVirtualForm = vfd;
0145: }
0146: }
0147:
0148: public void setVirtualForms(VirtualFormDescriptor[] vfds) {
0149: setVirtualForms(vfds, true);
0150: }
0151:
0152: private void setVirtualForms(VirtualFormDescriptor[] vfds,
0153: boolean sync) {
0154: super .setVirtualForms(vfds);
0155: if (sync) {
0156: String configStr = generateVirtualFormsConfig(vfds);
0157: setVirtualFormsConfig(configStr, false);
0158: }
0159: }
0160:
0161: public void setVirtualFormsConfig(String configStr) {
0162: setVirtualFormsConfig(configStr, true);
0163: }
0164:
0165: private void setVirtualFormsConfig(String configStr, boolean sync) {
0166: super .setVirtualFormsConfig(configStr);
0167: if (sync) {
0168: VirtualFormDescriptor[] vfds = generateVirtualForms(configStr);
0169: setVirtualForms(vfds, false);
0170: }
0171: }
0172:
0173: private VirtualFormDescriptor getVirtualFormComponentSubmitsByFullyQualifiedId(
0174: String fqId) {
0175: if (!isValidFullyQualifiedId(fqId)) {
0176: return null;
0177: }
0178:
0179: //first try regular configuration
0180: VirtualFormDescriptor vfd = getVirtualFormComponentSubmitsByFullyQualifiedId(
0181: fqId, getVirtualForms());
0182: if (vfd != null) {
0183: return vfd;
0184: }
0185:
0186: //try internal configuration
0187: vfd = getVirtualFormComponentSubmitsByFullyQualifiedId(fqId,
0188: getInternalVirtualForms());
0189: return vfd;
0190: }
0191:
0192: private VirtualFormDescriptor getVirtualFormComponentSubmitsByFullyQualifiedId(
0193: String fqId, VirtualFormDescriptor[] vfds) {
0194: if (vfds == null || vfds.length < 1) {
0195: return null;
0196: }
0197:
0198: //look for matches of fqId against all of vfds's submitters--without any trailing wilds.
0199: //if no match found, try the parent fqId.
0200: //this technique ensures that the most appropriate submitter is found first and its vf is returned
0201: String currentFqId = fqId;
0202: while (currentFqId.length() > 0) {
0203: for (int v = 0; v < vfds.length; v++) {
0204: VirtualFormDescriptor vfd = vfds[v];
0205: String[] submitters = vfd.getSubmittingIds();
0206: for (int s = 0; submitters != null
0207: && s < submitters.length; s++) {
0208: String submitter = submitters[s];
0209: if (submitter == null)
0210: continue;
0211: String wildSuffix = ID_SEP + ID_WILD;
0212: if (submitter.endsWith(wildSuffix)) {
0213: submitter = submitter.substring(0, submitter
0214: .length()
0215: - wildSuffix.length());
0216: }
0217: if (submitter.length() < 1)
0218: continue;
0219: boolean fqIdMatches = fullyQualifiedIdMatchesPattern(
0220: currentFqId, submitter);
0221: if (fqIdMatches) {
0222: return vfd;
0223: }
0224: }
0225: }
0226: int lastIndexOfSep = currentFqId.lastIndexOf(ID_SEP);
0227: currentFqId = currentFqId.substring(0, lastIndexOfSep);
0228: }
0229:
0230: return null;
0231: }
0232:
0233: private int getVirtualFormCount() {
0234: VirtualFormDescriptor[] vfds = getVirtualForms();
0235: VirtualFormDescriptor[] ivfds = getInternalVirtualForms();
0236: return (vfds == null ? 0 : vfds.length)
0237: + (ivfds == null ? 0 : ivfds.length);
0238: }
0239:
0240: private VirtualFormDescriptor getVirtualFormComponentSubmits(
0241: UIComponent component) {
0242: if (getVirtualFormCount() < 1) {
0243: return null;
0244: }
0245: String fqId = getFullyQualifiedId(component);
0246: VirtualFormDescriptor vfd = getVirtualFormComponentSubmitsByFullyQualifiedId(fqId);
0247: return vfd;
0248: }
0249:
0250: /** Get the virtual form submitted by the component whose id is provided or null if the component does not submit a virtual form. */
0251: public VirtualFormDescriptor getVirtualFormComponentSubmits(
0252: String id) {
0253: if (getVirtualFormCount() < 1) {
0254: return null;
0255: }
0256: if (isValidFullyQualifiedId(id)) {
0257: return getVirtualFormComponentSubmitsByFullyQualifiedId(id);
0258: }
0259: UIComponent component = findComponentById(id);
0260: return getVirtualFormComponentSubmits(component);
0261: }
0262:
0263: //this method is public for designer's use
0264: /**
0265: * Given a bare, partially qualified, or fully qualified id, find the component.
0266: * Unlike the inherited <code>findComponent</code> method, this method does recursively
0267: * search NamingContainers.
0268: */
0269: public UIComponent findComponentById(String id) {
0270: if (id == null) {
0271: return null;
0272: }
0273: if (id.length() == 0 || id.endsWith(ID_WILD)
0274: || (!id.equals(ID_SEP) && id.endsWith(ID_SEP))) {
0275: return null;
0276: }
0277: //see if id indicates the Form itself
0278: String fqId = getFullyQualifiedId(this );
0279: if (fullyQualifiedIdMatchesPattern(fqId, id)) {
0280: return this ;
0281: }
0282: return searchKidsRecursivelyForId(this , id);
0283: }
0284:
0285: private UIComponent searchKidsRecursivelyForId(UIComponent parent,
0286: String id) {
0287: Iterator kids = parent.getFacetsAndChildren();
0288: while (kids.hasNext()) {
0289: UIComponent kid = (UIComponent) kids.next();
0290: String fqId = getFullyQualifiedId(kid);
0291: //see if id indicates kid
0292: boolean fqIdMatches = fullyQualifiedIdMatchesPattern(fqId,
0293: id);
0294: if (fqIdMatches) {
0295: return kid;
0296: }
0297: UIComponent match = searchKidsRecursivelyForId(kid, id);
0298: if (match != null) {
0299: return match;
0300: }
0301: }
0302: return null;
0303: }
0304:
0305: //return true if a virtual form has been submitted and this component participates in that virtual form
0306: private boolean participatesInSubmittedVirtualForm(
0307: UIComponent component) {
0308: if (submittedVirtualForm == null) {
0309: return false;
0310: }
0311: String fqId = getFullyQualifiedId(component);
0312: return submittedVirtualForm.hasParticipant(fqId);
0313: }
0314:
0315: /**
0316: * <p>Generate an
0317: * array of <code>VirtualFormDescriptor</code>s based on a virtual form
0318: * configuration <code>String</code>.</p>
0319: */
0320: /*
0321: * Be sure to keep this method in sync with the version in
0322: * <code>javax.faces.component.html.HtmlFormDesignInfo</code>
0323: * (in jsfcl).</p>
0324: */
0325: public static VirtualFormDescriptor[] generateVirtualForms(
0326: String configStr) {
0327: //formname1|pid1 pid2 pid3|sid1 sid2 sid3, formname2|pid4 pid5 pid6|sid4 sid5 sid6
0328: if (configStr == null) {
0329: return null;
0330: }
0331: configStr = configStr.trim();
0332: if (configStr.length() < 1) {
0333: return new VirtualFormDescriptor[0];
0334: }
0335: //configStr now can't be null, blank, or just ws
0336:
0337: StringTokenizer st = new StringTokenizer(configStr, VF_DELIM_1);
0338: List vfs = new ArrayList(); //list of marshalled vfs
0339: while (st.hasMoreTokens()) {
0340: String vf = st.nextToken(); //not null, but could be just whitespace or blank
0341: vf = vf.trim();
0342: //vf could be a blank string
0343: if (vf.length() > 0) {
0344: vfs.add(vf);
0345: }
0346: }
0347:
0348: List descriptors = new ArrayList(); //a list of VirtualFormDescriptors
0349: for (int i = 0; i < vfs.size(); i++) { //go through each marshalled vf
0350: String vf = (String) vfs.get(i); //get the marshalled vf. not mere ws, blank, or null.
0351: st = new StringTokenizer(vf, VF_DELIM_2);
0352: String[] parts = new String[3]; //part1 is vf name, part2 is participating ids, part3 is submitting ids
0353: int partIndex = 0;
0354: while (partIndex < parts.length && st.hasMoreTokens()) {
0355: String part = st.nextToken(); //not null, but could be whitespace or blank
0356: part = part.trim(); //now can't be whitespace, but could be blank
0357: if (part.length() > 0) {
0358: //part is not null, whitespace, or blank
0359: parts[partIndex] = part;
0360: }
0361: partIndex++;
0362: }
0363:
0364: VirtualFormDescriptor vfd;
0365: if (parts[0] != null) {
0366: vfd = new VirtualFormDescriptor();
0367: vfd.setName(parts[0]); //won't be null, blank, or just ws
0368: descriptors.add(vfd);
0369: } else {
0370: continue; //this marshalled vf has no name. can't create a descriptor for it. go to next marshalled vf
0371: }
0372:
0373: if (parts[1] != null) {
0374: String pidString = parts[1]; //not null, blank, or just ws
0375: st = new StringTokenizer(pidString);
0376: List pidList = new ArrayList();
0377: while (st.hasMoreTokens()) {
0378: String pid = st.nextToken();
0379: pidList.add(pid.trim());
0380: }
0381: String[] pids = (String[]) pidList
0382: .toArray(new String[pidList.size()]); //size guaranteed to be at least 1
0383: vfd.setParticipatingIds(pids);
0384: }
0385:
0386: if (parts[2] != null) {
0387: String sidString = parts[2]; //not null, blank, or just ws
0388: st = new StringTokenizer(sidString);
0389: List sidList = new ArrayList();
0390: while (st.hasMoreTokens()) {
0391: String sid = st.nextToken();
0392: sidList.add(sid.trim());
0393: }
0394: String[] sids = (String[]) sidList
0395: .toArray(new String[sidList.size()]); //size guaranteed to be at least 1
0396: vfd.setSubmittingIds(sids);
0397: }
0398: }
0399: return (VirtualFormDescriptor[]) descriptors
0400: .toArray(new VirtualFormDescriptor[descriptors.size()]); //might be of size 0, but won't be null
0401: }
0402:
0403: /**
0404: * <p>Generate a virtual form
0405: * configuration <code>String</code> based on an
0406: * array of <code>VirtualFormDescriptor</code>s.</p>
0407: */
0408: /*
0409: * Be sure to keep this method in sync with the version in
0410: * <code>javax.faces.component.html.HtmlFormDesignInfo</code>
0411: * (in jsfcl).</p>
0412: */
0413: public static String generateVirtualFormsConfig(
0414: VirtualFormDescriptor[] descriptors) {
0415: if (descriptors == null) {
0416: return null;
0417: }
0418: StringBuffer sb = new StringBuffer();
0419: for (int i = 0; i < descriptors.length; i++) {
0420: if (descriptors[i] != null) {
0421: String vf = descriptors[i].toString();
0422: if (vf.length() > 0) {
0423: if (sb.length() > 0) {
0424: sb.append(" , ");
0425: }
0426: sb.append(vf);
0427: }
0428: }
0429: }
0430: return sb.toString();
0431: }
0432:
0433: /**
0434: * Obtain the virtual form compatible fully-qualified id for the supplied component.
0435: * A fully-qualified id begins with the <code>NamingContainer.SEPARATOR_CHAR</code>
0436: * (representing the <code>Form</code> itself), contains component ids of the
0437: * component's ancestors separated by <code>NamingContainer.SEPARATOR_CHAR</code>,
0438: * and ends with the component's id.
0439: */
0440: /*
0441: * Be sure to keep this method in sync with the versions in
0442: * <code>javax.faces.component.html.HtmlFormDesignInfo</code>
0443: * (in jsfcl) and
0444: * <code>com.sun.rave.web.ui.component.FormDesignInfo</code>
0445: * (in webui).</p>
0446: */
0447: public static String getFullyQualifiedId(UIComponent component) {
0448: if (component == null) {
0449: return null;
0450: }
0451: if (component instanceof Form) {
0452: return ID_SEP;
0453: }
0454: String compId = component.getId();
0455: if (compId == null) {
0456: return null;
0457: }
0458: StringBuffer sb = new StringBuffer(compId);
0459: UIComponent currentComp = component.getParent();
0460: boolean formEncountered = false;
0461: while (currentComp != null) {
0462: sb.insert(0, ID_SEP);
0463: if (currentComp instanceof Form) {
0464: formEncountered = true;
0465: break;
0466: } else {
0467: String currentCompId = currentComp.getId();
0468: if (currentCompId == null) {
0469: return null;
0470: }
0471: sb.insert(0, currentCompId);
0472: }
0473: currentComp = currentComp.getParent();
0474: }
0475: if (formEncountered) {
0476: return sb.toString();
0477: } else {
0478: return null;
0479: }
0480: }
0481:
0482: /**
0483: * Determine if the id provided is non-null and exhibits the traits of a
0484: * fully qualified id. This includes beginning with
0485: * <code>NamingContainer.SEPARATOR_CHAR</code>, not ending with that
0486: * character unless it is the only character, not ending in
0487: * <code>Form.ID_WILD_CHAR</code>, and not containing spaces.
0488: */
0489: /*
0490: * Be sure to keep this method in sync with the version in
0491: * <code>javax.faces.component.html.HtmlFormDesignInfo</code>
0492: * (in jsfcl).</p>
0493: */
0494: public static boolean isValidFullyQualifiedId(String id) {
0495: return id != null && id.startsWith(ID_SEP)
0496: && (id.length() == 1 || !id.endsWith(ID_SEP))
0497: && !id.endsWith(ID_WILD) && id.indexOf(' ') == -1;
0498: }
0499:
0500: /**
0501: * Determine if the fully qualified id provided matches the supplied pattern.
0502: * The pattern may be a bare, partially qualified, or fully qualified id.
0503: * The pattern may also end with the
0504: * <code>NamingContainer.SEPARATOR_CHAR</code> followed by the
0505: * <code>Form.ID_WILD_CHAR</code>, in which case the children of the component
0506: * indicated by the pattern will be considered a match.
0507: */
0508: /*
0509: * Be sure to keep this method in sync with the version in
0510: * <code>javax.faces.component.html.HtmlFormDesignInfo</code>
0511: * (in jsfcl).</p>
0512: */
0513: public static boolean fullyQualifiedIdMatchesPattern(String fqId,
0514: String pattern) {
0515: if (!isValidFullyQualifiedId(fqId)) {
0516: return false;
0517: }
0518: if (pattern == null || pattern.length() < 1
0519: || pattern.indexOf(' ') != -1) {
0520: return false;
0521: }
0522: //unless pattern is ":", it should not end with ":"
0523: if (pattern.endsWith(ID_SEP) && !pattern.equals(ID_SEP)) {
0524: return false;
0525: }
0526:
0527: String wildSuffix = ID_SEP + ID_WILD;
0528:
0529: //if ID_WILD appears in pattern, it must be the last character, and preceded by ID_SEP
0530: int indexOfWildInPattern = pattern.indexOf(ID_WILD);
0531: if (indexOfWildInPattern != -1) {
0532: if (indexOfWildInPattern != pattern.length() - 1) {
0533: return false;
0534: }
0535: if (!pattern.endsWith(wildSuffix)) {
0536: return false;
0537: }
0538: }
0539:
0540: if (pattern.equals(wildSuffix)) {
0541: //if pattern was ":*", then any valid fqId is a match
0542: return true;
0543: } else if (pattern.endsWith(wildSuffix)) {
0544: String patternPrefix = pattern.substring(0, pattern
0545: .length()
0546: - wildSuffix.length());
0547: if (patternPrefix.startsWith(ID_SEP)) {
0548: return fqId.equals(patternPrefix)
0549: || fqId.startsWith(patternPrefix + ID_SEP);
0550: } else {
0551: return fqId.endsWith(ID_SEP + patternPrefix)
0552: || fqId
0553: .indexOf(ID_SEP + patternPrefix
0554: + ID_SEP) > -1;
0555: }
0556: } else {
0557: if (pattern.startsWith(ID_SEP)) {
0558: return fqId.equals(pattern);
0559: } else {
0560: return fqId.endsWith(ID_SEP + pattern);
0561: }
0562: }
0563: }
0564:
0565: /**
0566: * Add a <code>VirtualFormDescriptor</code> to the internal virtual forms.
0567: * If an existing VirtualFormDescriptor object is found with the same name,
0568: * the object is replaced.
0569: *
0570: * @param descriptor The <code>VirtualFormDescriptor</code> to add.
0571: */
0572: public void addInternalVirtualForm(VirtualFormDescriptor descriptor) {
0573: if (descriptor == null) {
0574: return;
0575: }
0576:
0577: // Get current descriptors.
0578: VirtualFormDescriptor[] oldDescriptors = getInternalVirtualForms();
0579:
0580: // Iterate over each VirtualFormDescriptor object and check for a match.
0581: if (oldDescriptors != null) {
0582: for (int i = 0; i < oldDescriptors.length; i++) {
0583: if (oldDescriptors[i] == null) {
0584: continue;
0585: }
0586: String name = oldDescriptors[i].getName();
0587: if (name != null && name.equals(descriptor.getName())) {
0588: oldDescriptors[i] = descriptor;
0589: return; // No further processing is required.
0590: }
0591: }
0592: }
0593:
0594: // Create array to hold new descriptors.
0595: int oldLength = (oldDescriptors != null) ? oldDescriptors.length
0596: : 0;
0597: VirtualFormDescriptor[] newDescriptors = new VirtualFormDescriptor[oldLength + 1];
0598: for (int i = 0; i < oldLength; i++) {
0599: newDescriptors[i] = oldDescriptors[i];
0600: }
0601:
0602: // Add new VirtualFormDescriptor object.
0603: newDescriptors[oldLength] = descriptor;
0604: setInternalVirtualForms(newDescriptors);
0605: }
0606:
0607: /*
0608: * Be sure to keep this class in sync with the version in
0609: * <code>javax.faces.component.html.HtmlFormDesignInfo</code>
0610: * (in jsfcl).</p>
0611: */
0612: public static class VirtualFormDescriptor implements Serializable {
0613: private String name; //name of the virtual form
0614: private String[] participatingIds; //ids of components that participate
0615: private String[] submittingIds; //ids of components that submit
0616:
0617: public VirtualFormDescriptor() {
0618: }
0619:
0620: public VirtualFormDescriptor(String name) {
0621: setName(name);
0622: }
0623:
0624: public String getName() {
0625: return name;
0626: }
0627:
0628: public void setName(String name) {
0629: if (name == null) {
0630: throw new IllegalArgumentException(getMessage(
0631: "nullVfName", null) //NOI18N
0632: );
0633: }
0634: name = name.trim();
0635: if (name.length() < 1) {
0636: throw new IllegalArgumentException(getMessage(
0637: "vfNameWhitespaceOnly", null) //NOI18N
0638: );
0639: }
0640: this .name = name;
0641: }
0642:
0643: public String[] getParticipatingIds() {
0644: return participatingIds;
0645: }
0646:
0647: public void setParticipatingIds(
0648: java.lang.String[] participatingIds) {
0649: for (int i = 0; participatingIds != null
0650: && i < participatingIds.length; i++) {
0651: if (participatingIds[i] == null) {
0652: throw new IllegalArgumentException(getMessage(
0653: "nullParticipatingIdAtIndex",
0654: new Object[] { new Integer(i) }) //NOI18N
0655: );
0656: }
0657: participatingIds[i] = participatingIds[i].trim();
0658: if (participatingIds[i].length() < 1) {
0659: throw new IllegalArgumentException(getMessage(
0660: "whitespaceOnlyParticipatingIdAtIndex",
0661: new Object[] { new Integer(i) }) //NOI18N
0662: );
0663: }
0664: }
0665: this .participatingIds = participatingIds;
0666: }
0667:
0668: public String[] getSubmittingIds() {
0669: return submittingIds;
0670: }
0671:
0672: public void setSubmittingIds(java.lang.String[] submittingIds) {
0673: for (int i = 0; submittingIds != null
0674: && i < submittingIds.length; i++) {
0675: if (submittingIds[i] == null) {
0676: throw new IllegalArgumentException(getMessage(
0677: "nullSubmittingIdAtIndex",
0678: new Object[] { new Integer(i) }) //NOI18N
0679: );
0680: }
0681: submittingIds[i] = submittingIds[i].trim();
0682: if (submittingIds[i].length() < 1) {
0683: throw new IllegalArgumentException(getMessage(
0684: "whitespaceOnlySubmittingIdAtIndex",
0685: new Object[] { new Integer(i) }) //NOI18N
0686: );
0687: }
0688: }
0689: this .submittingIds = submittingIds;
0690: }
0691:
0692: //return true if the component id provided submits this virtual form
0693: public boolean isSubmittedBy(String fqId) {
0694: if (!isValidFullyQualifiedId(fqId))
0695: return false;
0696: for (int i = 0; submittingIds != null
0697: && i < submittingIds.length; i++) {
0698: if (Form.fullyQualifiedIdMatchesPattern(fqId,
0699: submittingIds[i])) {
0700: return true;
0701: }
0702: }
0703: return false;
0704: }
0705:
0706: //return true if the component id provided participates in this virtual form
0707: public boolean hasParticipant(String fqId) {
0708: if (!isValidFullyQualifiedId(fqId))
0709: return false;
0710: for (int i = 0; participatingIds != null
0711: && i < participatingIds.length; i++) {
0712: if (Form.fullyQualifiedIdMatchesPattern(fqId,
0713: participatingIds[i])) {
0714: return true;
0715: }
0716: }
0717: return false;
0718: }
0719:
0720: public String toString() {
0721: if (name == null) {
0722: return "";
0723: } //NOI18N
0724: StringBuffer sb = new StringBuffer();
0725: sb.append(name);
0726: sb.append(" | "); //NOI18N
0727: for (int i = 0; participatingIds != null
0728: && i < participatingIds.length; i++) {
0729: sb.append(participatingIds[i]);
0730: sb.append(' ');
0731: }
0732: sb.append("| "); //NOI18N
0733: for (int i = 0; submittingIds != null
0734: && i < submittingIds.length; i++) {
0735: sb.append(submittingIds[i]);
0736: sb.append(' ');
0737: }
0738: return sb.toString().trim();
0739: }
0740: }
0741:
0742: //Examine the participating ids that end in ID_WILD. If any of them match
0743: //the component's fully qualified id, then the component's children are known
0744: //to participate in vfd.
0745: private static boolean childrenAreKnownToParticipate(
0746: UIComponent component, VirtualFormDescriptor vfd) {
0747: if (vfd == null) {
0748: return false;
0749: }
0750:
0751: String fqId = getFullyQualifiedId(component);
0752: if (fqId == null) {
0753: return false;
0754: }
0755:
0756: String[] participants = vfd.getParticipatingIds();
0757: for (int i = 0; participants != null && i < participants.length; i++) {
0758: String participant = participants[i];
0759: String wildSuffix = ID_SEP + ID_WILD;
0760: if (participant == null
0761: || !participant.endsWith(wildSuffix)) {
0762: continue;
0763: }
0764: if (fullyQualifiedIdMatchesPattern(fqId, participant)) {
0765: return true;
0766: }
0767: }
0768:
0769: return false;
0770: }
0771:
0772: /**
0773: * <p>Recursively erase virtual form non-participants by setting their
0774: * submitted values to <code>null</code>. This method caches the submitted
0775: * values in the <code>erasedMap</code> before actually erasing.
0776: * If the <code>parent</code> is embedded in one or more tables,
0777: * the <code>contextualTables</code> and <code>contextualRows</code>
0778: * are used to record the table- and row-based context of the
0779: * submitted value, so that such context can be stored in the
0780: * <code>erasedMap</code>.</p>
0781: * <p><b>Note:</b> Restoring of submitted values works for
0782: * the braveheart table but not the standard jsf table. However, submitted
0783: * values inside a standard jsf table are still cached and an attempt is
0784: * made to restore them, in case a third-party component extends
0785: * <code>UIData</code> and, unlike <code>UIData</code>, does not discard
0786: * its saved state during rendering.</p>
0787: * @param parent A parent component whose children will be examined and
0788: * possibly erased
0789: * @param contextualTables an array of UIData or TableRowGroup components
0790: * in the parent's ancestry (with the most distant ancestor as the first
0791: * member of the array), or <code>null</code> if the parent is not embedded
0792: * within any tables
0793: * @param contextualRows a parallel array of <code>Integer</code> or
0794: * <code>RowKey</code> objects representing a row in the corresponding
0795: * contextual table, or <code>null</code> if the parent is not embedded
0796: * within any tables
0797: */
0798: private void eraseVirtualFormNonParticipants(UIComponent parent,
0799: Object[] contextualTables, Object[] contextualRows) {
0800: // Process all facets and children of this component
0801: synchronized (erasedMap) { //prevent multiple threads from the same session simultaneously accessing erasedMap, nonDefaultRetainStatusEvhs
0802: Iterator kids = parent.getFacetsAndChildren();
0803: while (kids.hasNext()) {
0804: UIComponent kid = (UIComponent) kids.next();
0805:
0806: //if this kid is an EditableValueHolder, and it does not participate, set submitted value to null
0807: if (kid instanceof EditableValueHolder
0808: && !participatesInSubmittedVirtualForm(kid)) {
0809: EditableValueHolder kidEvh = (EditableValueHolder) kid;
0810: //cache the submitted value to be erased in eraseMap
0811: Object submittedValueToErase = kidEvh
0812: .getSubmittedValue();
0813: if (contextualTables == null) {
0814: erasedMap.put(kidEvh, submittedValueToErase);
0815: } else {
0816: addTableValuesEntry(erasedMap, kidEvh, 0,
0817: contextualTables, contextualRows,
0818: submittedValueToErase);
0819: }
0820: kidEvh.setSubmittedValue(null);
0821: }
0822:
0823: //if children of kid are known to participate in submittedVirtualForm,
0824: //then no need to recurse on kid
0825: if (childrenAreKnownToParticipate(kid,
0826: submittedVirtualForm)) {
0827: continue; //continue to next kid
0828: }
0829:
0830: //recurse. if kid is a UIData or TableRowGroup, perform a recursive call once per row.
0831: //if kid is not a UIData or TableRowGroup, simply perform a recursive call once.
0832: if (kid instanceof UIData) {
0833: UIData kidTable = (UIData) kid;
0834: int originalRowIndex = kidTable.getRowIndex();
0835: int rowIndex = 0;
0836: kidTable.setRowIndex(rowIndex);
0837: while (kidTable.isRowAvailable()) {
0838: Object[] localContextualTables = appendToArray(
0839: contextualTables, kidTable);
0840: Object[] localContextualRows = appendToArray(
0841: contextualRows, new Integer(rowIndex));
0842: eraseVirtualFormNonParticipants(kidTable,
0843: localContextualTables,
0844: localContextualRows);
0845: kidTable.setRowIndex(++rowIndex);
0846: }
0847: kidTable.setRowIndex(originalRowIndex);
0848: } else if (kid instanceof TableRowGroup) {
0849: TableRowGroup group = (TableRowGroup) kid;
0850: RowKey[] rowKeys = group.getRowKeys();
0851: RowKey oldRowKey = group.getRowKey(); // Save RowKey.
0852:
0853: // Check for null TableDataProvider.
0854: if (rowKeys != null) {
0855: for (int i = 0; i < rowKeys.length; i++) {
0856: group.setRowKey(rowKeys[i]);
0857: if (!group.isRowAvailable()) {
0858: continue;
0859: }
0860: Object[] localContextualTables = appendToArray(
0861: contextualTables, group);
0862: Object[] localContextualRows = appendToArray(
0863: contextualRows, rowKeys[i]);
0864: eraseVirtualFormNonParticipants(group,
0865: localContextualTables,
0866: localContextualRows);
0867: }
0868: }
0869: group.setRowKey(oldRowKey); // Restore RowKey.
0870: } else {
0871: eraseVirtualFormNonParticipants(kid,
0872: contextualTables, contextualRows);
0873: }
0874: }
0875: }
0876: }
0877:
0878: /** <p>Recursively add an entry to <code>erasedMap</code>, whose value is a
0879: * <code>TableValues</code> (and whose key is an
0880: * <code>EditableValueHolder</code>).</p>
0881: */
0882: private void addTableValuesEntry(Map map, Object mapKey, int c,
0883: Object[] contextualTables, Object[] contextualRows,
0884: Object submittedValueToErase) {
0885: //use the map and mapKey to get a TableValues, using contextualTables[c] to create tsv if necessary
0886: TableValues tv = (TableValues) map.get(mapKey);
0887: if (tv == null) {
0888: tv = new TableValues(contextualTables[c]);
0889: map.put(mapKey, tv);
0890: }
0891:
0892: //ensure an entry is populated in tv.values with contextualRows[c] as the key
0893: Map values = tv.getValues();
0894: if (c == contextualTables.length - 1) { //if last index in contextualTables
0895: values.put(contextualRows[c], submittedValueToErase);
0896: } else {
0897: addTableValuesEntry(values, contextualRows[c], c + 1,
0898: contextualTables, contextualRows,
0899: submittedValueToErase);
0900: }
0901: }
0902:
0903: private static Object[] appendToArray(Object[] array, Object item) {
0904: Object[] result;
0905: if (array == null) {
0906: result = new Object[] { item };
0907: } else {
0908: result = new Object[array.length + 1];
0909: System.arraycopy(array, 0, result, 0, array.length);
0910: result[array.length] = item;
0911: }
0912: return result;
0913: }
0914:
0915: /**
0916: * <p>Restore the submitted values erased by the virtual form mechanism
0917: * where appropriate.
0918: * This method is called in <code>FormRenderer.renderStart</code>. It should
0919: * not be called by developer code.</p>
0920: * <p><b>Note:</b> Restoring of submitted values works on
0921: * <code>TableRowGroup</code> components, but does not work on the
0922: * standard faces data table component. This is because in
0923: * <code>UIData.encodeBegin</code>, the table's per-row saved state is
0924: * typically discarded. The result is that upon
0925: * exiting <code>FormRenderer.renderStart</code>, the submitted values will
0926: * be restored; however, they will subsequently be discarded.
0927: * Nonetheless, we still cache and restore those submitted values, in case
0928: * a third-party component extends
0929: * <code>UIData</code> and, unlike <code>UIData</code>, does not discard
0930: * its saved state during rendering.</p>
0931: */
0932: public void restoreNonParticipatingSubmittedValues() {
0933: synchronized (erasedMap) { //prevent multiple threads from the same session simultaneously accessing erasedMap, nonDefaultRetainStatusEvhs
0934: for (Iterator iter = erasedMap.entrySet().iterator(); iter
0935: .hasNext();) {
0936: Map.Entry entry = (Map.Entry) iter.next();
0937: EditableValueHolder evh = (EditableValueHolder) entry
0938: .getKey();
0939: //if evh is designated as discarding submitted values, do not restore
0940: //if DEFAULT_RETAIN_STATUS==true (retain by default), then nonDefaultRetainStatusEvhs contains evhs that discard
0941: //if DEFAULT_RETAIN_STATUS==false (discard by default), then nonDefaultRetainStatusEvhs contains evhs that retain
0942: boolean evhAppearsInSet = nonDefaultRetainStatusEvhs
0943: .contains(evh);
0944: boolean discards = DEFAULT_RETAIN_STATUS ? evhAppearsInSet
0945: : !evhAppearsInSet;
0946: if (discards) {
0947: continue;
0948: }
0949: //restore
0950: Object erasedMapValue = entry.getValue();
0951: if (erasedMapValue instanceof TableValues) {
0952: TableValues tv = (TableValues) erasedMapValue;
0953: restoreTableValues(tv, evh);
0954: } else {
0955: evh.setSubmittedValue(erasedMapValue);
0956: }
0957: }
0958: nonDefaultRetainStatusEvhs.clear(); //after restoring, clear out the retain status data
0959: }
0960: }
0961:
0962: /**
0963: * <p>Helper method to restore submitted values for an
0964: * <code>EditableValueHolder</code> component from a
0965: * <code>TableValues</code> object, which contains a value for
0966: * each row of each table in the <code>EditableValueHolder</code>'s
0967: * ancestry.</p>
0968: */
0969: private void restoreTableValues(TableValues tv,
0970: EditableValueHolder evh) {
0971: //capture the old row of the tv and
0972: //iterate through the tv rows
0973: Object oldRow; //an Integer or RowKey
0974: Iterator rowIterator;
0975: Object table = tv.getTable();
0976: Map values = tv.getValues();
0977: if (table instanceof UIData) {
0978: //capture the old row
0979: UIData uidata = (UIData) table;
0980: int iOldRow = uidata.getRowIndex();
0981: oldRow = new Integer(iOldRow);
0982:
0983: //get rowIterator
0984: List rowList = new ArrayList();
0985: rowList.addAll(values.keySet()); //add Set of Integers to List
0986: Collections.sort(rowList);
0987: rowIterator = rowList.iterator();
0988: } else {
0989: //capture the old row
0990: TableRowGroup rowGroup = (TableRowGroup) table;
0991: oldRow = rowGroup.getRowKey();
0992:
0993: //get rowIterator
0994: rowIterator = values.keySet().iterator();
0995: }
0996:
0997: while (rowIterator.hasNext()) {
0998: Object row = rowIterator.next(); //row is an Integer or RowKey
0999:
1000: //set the table to that row
1001: if (table instanceof UIData) {
1002: UIData uidata = (UIData) table;
1003: Integer rowInt = (Integer) row;
1004: int iRow = rowInt.intValue();
1005: uidata.setRowIndex(iRow);
1006: } else {
1007: TableRowGroup rowGroup = (TableRowGroup) table;
1008: RowKey rowKey = (RowKey) row;
1009: rowGroup.setRowKey(rowKey);
1010: }
1011:
1012: //get the rowValue for that row, which can be an Object or a TableValues
1013: //and restore that rowValue
1014: Object rowValue = values.get(row);
1015: if (rowValue instanceof TableValues) {
1016: TableValues rowValueTv = (TableValues) rowValue;
1017: restoreTableValues(rowValueTv, evh);
1018: } else {
1019: evh.setSubmittedValue(rowValue);
1020: }
1021: }
1022:
1023: //set table back to old row
1024: if (table instanceof UIData) {
1025: UIData uidata = (UIData) table;
1026: Integer oldRowInt = (Integer) oldRow;
1027: int iOldRow = oldRowInt.intValue();
1028: uidata.setRowIndex(iOldRow);
1029: } else {
1030: TableRowGroup rowGroup = (TableRowGroup) table;
1031: RowKey oldRowKey = (RowKey) oldRow;
1032: rowGroup.setRowKey(oldRowKey);
1033: }
1034: }
1035:
1036: /** <p>Structure that stores a component's submitted value
1037: * for each row of a table (<code>UIData</code> or
1038: * <code>TableRowGroup</code>), either as an
1039: * <code>Object</code> or as a
1040: * nested <code>TableValues</code>. This permits storing of all the
1041: * submitted values for a component in the case where that component has
1042: * more than one table in its ancestry.</p>
1043: */
1044: private class TableValues {
1045: private Object table; //store a UIData or TableRowGroup
1046: private Map values; //has a RowKey or Integer as the key, and a submitted value Object or a TableValues as the value
1047:
1048: public TableValues(Object table) {
1049: this .table = table;
1050: values = new HashMap();
1051: }
1052:
1053: /** <p>Get the <code>UIData</code> or <code>TableRowGroup</code>
1054: * for this instance.</p>
1055: */
1056: public Object getTable() {
1057: return this .table;
1058: }
1059:
1060: /** <p>Get a <code>Map</code> of the values for each row
1061: * of the table in question, where the key is an <code>Integer</code> or
1062: * <code>RowKey</code>, and the value is a submitted value
1063: * <code>Object</code> or a
1064: * <code>TableValues</code> instance.</p>
1065: */
1066: public Map getValues() {
1067: return this .values;
1068: }
1069: }
1070:
1071: /**
1072: * <p>Ensure that the supplied <code>EditableValueHolder</code> component
1073: * will discard (rather than retain) its submitted value when a virtual
1074: * form is submitted in which the component does not participate.</p>
1075: * @param inputField An <code>EditableValueHolder</code> component that is
1076: * <strong>not</strong> a participant in the virtual form that was submitted
1077: * on this request.
1078: * @throws IllegalArgumentException if inputField is null.
1079: * @throws IllegalArgumentException if a virtual form has been
1080: * submitted and the supplied inputField participates in it.
1081: */
1082: public void discardSubmittedValue(EditableValueHolder inputField) {
1083: if (inputField == null) {
1084: throw new IllegalArgumentException(getMessage(
1085: "nullInputField", null) //NOI18N
1086: );
1087: }
1088: if (inputField instanceof UIComponent) { //just defensive
1089: UIComponent uicInputField = (UIComponent) inputField;
1090: if (participatesInSubmittedVirtualForm(uicInputField)) {
1091: throw new IllegalArgumentException(getMessage(
1092: "supplyNonParticipatingInputField",
1093: new Object[] { uicInputField.getId(),
1094: "discardSubmittedValue" }) //NOI18N
1095: );
1096:
1097: }
1098: }
1099: List evhCollection = new ArrayList();
1100: evhCollection.add(inputField);
1101: setRetainWhenNonParticipating(evhCollection, false);
1102: }
1103:
1104: /**
1105: * <p>Ensure that the participants in the supplied virtual form
1106: * will discard (rather than retain) their submitted values when a
1107: * different virtual form is submitted.</p>
1108: * @param virtualFormName The name of a virtual form on this page which
1109: * has not been submitted.
1110: * @throws IllegalArgumentException if no virtual form exists with the
1111: * supplied name.
1112: * @throws IllegalArgumentException if the supplied virtual form has been
1113: * submitted on this request.
1114: */
1115: public void discardSubmittedValues(String virtualFormName) {
1116: VirtualFormDescriptor vfd = getVirtualFormByName(virtualFormName);
1117: if (vfd == null) {
1118: throw new IllegalArgumentException(getMessage(
1119: "unrecognizedVfName",
1120: new Object[] { virtualFormName }) //NOI18N
1121: );
1122: }
1123: if (vfd == submittedVirtualForm) {
1124: throw new IllegalArgumentException(
1125: getMessage("supplyUnsubmittedVirtualForm",
1126: new Object[] { virtualFormName,
1127: "discardSubmittedValues" }) //NOI18N
1128: );
1129: }
1130: setRetainWhenNonParticipating(vfd, false);
1131: }
1132:
1133: private void setRetainWhenNonParticipating(
1134: VirtualFormDescriptor vfd, boolean retain) {
1135: String[] pids = vfd.getParticipatingIds();
1136: if (pids == null || pids.length < 1) {
1137: return;
1138: }
1139: List evhList = new ArrayList();
1140: for (int i = 0; i < pids.length; i++) {
1141: UIComponent uic = findComponentById(pids[i]);
1142: if (uic instanceof EditableValueHolder) {
1143: evhList.add(uic);
1144: }
1145: }
1146: setRetainWhenNonParticipating(evhList, retain);
1147: }
1148:
1149: private void setRetainWhenNonParticipating(Collection evhs,
1150: boolean retain) {
1151: if (evhs == null || evhs.size() < 1) {
1152: return;
1153: }
1154: synchronized (erasedMap) { //prevent multiple threads from the same session simultaneously accessing erasedMap, nonDefaultRetainStatusEvhs
1155: //if we retain by default, then nonDefaultRetainStatusEvhs should contain evhs only if we want evhs to discard
1156: //if we discard by default, then nonDefaultRetainStatusEvhs should contain evhs only if we want evhs to retain
1157: boolean shouldContain = DEFAULT_RETAIN_STATUS ? !retain
1158: : retain;
1159: if (shouldContain) {
1160: nonDefaultRetainStatusEvhs.addAll(evhs);
1161: } else {
1162: nonDefaultRetainStatusEvhs.removeAll(evhs);
1163: }
1164: }
1165: }
1166:
1167: private VirtualFormDescriptor getVirtualFormByName(
1168: String virtualFormName) {
1169: if (virtualFormName == null) {
1170: return null;
1171: }
1172: VirtualFormDescriptor[] vfds = getVirtualForms();
1173: for (int i = 0; vfds != null && i < vfds.length; i++) {
1174: if (virtualFormName.equals(vfds[i].getName())) {
1175: return vfds[i];
1176: }
1177: }
1178: return null;
1179: }
1180:
1181: private static String getMessage(String key, Object[] args) {
1182: String baseName = Form.class.getPackage().getName() + ".Bundle";
1183: return MessageUtil.getMessage(
1184: FacesContext.getCurrentInstance(), baseName, key, args);
1185: }
1186:
1187: public String getEnctype() {
1188: String encType = super .getEnctype();
1189: if (encType == null || encType.length() == 0) {
1190: encType = "application/x-www-form-urlencoded"; //NOI18N
1191: super.setEnctype(encType);
1192: }
1193: return encType;
1194: }
1195: }
|