001: /* LeafComponent.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Aug 7, 2007 5:56:04 PM 2007, Created by Dennis.Chen
010: }}IS_NOTE
011:
012: Copyright (C) 2007 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.jsf.zul.impl;
020:
021: import java.io.IOException;
022: import java.io.StringWriter;
023: import java.io.Writer;
024: import java.util.Iterator;
025: import java.util.LinkedHashMap;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Map.Entry;
029:
030: import javax.faces.component.UIComponent;
031: import javax.faces.component.UIForm;
032: import javax.faces.context.FacesContext;
033: import javax.faces.context.ResponseWriter;
034: import javax.faces.el.ValueBinding;
035:
036: import org.zkoss.lang.reflect.Fields;
037: import org.zkoss.util.ModificationException;
038: import org.zkoss.zk.ui.Component;
039: import org.zkoss.zk.ui.Executions;
040: import org.zkoss.zk.ui.event.CreateEvent;
041: import org.zkoss.zk.ui.event.Events;
042: import org.zkoss.zk.ui.ext.AfterCompose;
043: import org.zkoss.zk.ui.ext.DynamicPropertied;
044: import org.zkoss.zk.ui.metainfo.ComponentDefinition;
045: import org.zkoss.zk.ui.metainfo.EventHandler;
046: import org.zkoss.zk.ui.metainfo.ZScript;
047: import org.zkoss.zk.ui.metainfo.impl.AnnotationHelper;
048: import org.zkoss.zk.ui.sys.ComponentCtrl;
049: import org.zkoss.zk.ui.sys.ComponentsCtrl;
050:
051: /**
052: * The skeletal class used to implement the ZULJSF Component for ZK components
053: * that don't accept any child.
054: * @author Dennis.Chen
055: *
056: */
057: abstract public class LeafComponent extends AbstractComponent {
058:
059: /*
060: * ZUL Component for RenderPhase Only.
061: */
062: /*package*/Component _zulcomp;
063:
064: /*
065: * Root ZUL JSF component of this ZUL JSF component
066: */
067: /*package*/RootComponent _rootcomp;
068:
069: /*
070: * Parent ZUL component of this ZUL JSF component
071: */
072: /*package*/BranchComponent _parentcomp;
073:
074: /*
075: * original attribute of this ZULJSF component.
076: * contains attribute and event (ex.onClick)
077: */
078: protected Map _compAttrMap;
079:
080: //attribute set by custom-attributes
081: private Map _compCustomAttributes;
082:
083: private String _use;
084:
085: private String _forward;
086:
087: //if id attribute is seted, when we must set id to zul component.
088: /*package*/boolean _idSet = false;
089:
090: protected ComposerHandler _composer = null;
091:
092: /** Returns the RootComponent that this Component belongs to.
093: */
094: /*package*/RootComponent getRootComponent() {
095: return _rootcomp;
096: }
097:
098: /** Returns the Parent Component
099: */
100: /*package*/BranchComponent getParentComponent() {
101: return _parentcomp;
102: }
103:
104: /**
105: * get the component definition name of this component<br/>
106: * The default implementation change the class name to lower char and return<br/>
107: * @return a ZUL component name
108: */
109: protected String getComponentDefName() {
110: String name = getClass().getSimpleName();
111: return name.toLowerCase();
112: }
113:
114: StringWriter fakeSw = null;
115:
116: ResponseWriter fakeOw = null;
117:
118: /**
119: * Override method,
120: * We Construct ZUL JSF Component tree here.
121: * This method is called by JSF implementation, deriving class rarely need to invoke this method.
122: */
123: public void encodeBegin(FacesContext context) throws IOException {
124: /*
125: * UIComponentTagBase is the base class for all JSP tags that use
126: * the "classic" JSP tag interface that correspond to a UIComponent instance in the view.
127: * In Faces 1.2, all component tags are BodyTag instances to allow for the execution of the page to build the component tree,
128: * but not render it. Rendering happens only after the component tree is completely built.
129: *
130: * In UIComponentClassicTagBase doStartTag phrase, inline text which appears before tag will be generated to
131: * a verbatim and attached to its parent. Then, it will be cleared. In order to adapt JSF 1.2,
132: * a temporary StringWriter is needed for holding the content and assigns the content to bodyContent in
133: * encodeEnd()
134: */
135: fakeSw = new StringWriter();
136:
137: fakeOw = context.getResponseWriter();
138:
139: context.setResponseWriter(fakeOw.cloneWithWriter(fakeSw));
140:
141: if (!isRendered() || !isEffective())
142: return; //nothing to do
143: super .encodeBegin(context);
144: final AbstractComponent ac = (AbstractComponent) findAncestorWithClass(
145: this , AbstractComponent.class);
146: if (ac instanceof RootComponent) { //root component tag
147: _rootcomp = (RootComponent) ac;
148: _parentcomp = null;
149: } else if (ac instanceof BranchComponent) {
150: _parentcomp = (BranchComponent) ac;
151: _rootcomp = _parentcomp.getRootComponent();
152: } else {
153: throw new IllegalStateException(
154: "Must be nested inside the page component or branch component: "
155: + this );
156: }
157:
158: //keep component tree structure for ZULJSF Component
159: ComponentInfo cinfo = getComponentInfo();
160: if (cinfo != null) {
161: if (_parentcomp != null) {
162: cinfo.addChildInfo(_parentcomp, this );
163: } else if (_rootcomp != null) {
164: cinfo.addChildInfo(_rootcomp, this );
165: }
166: }
167: }
168:
169: /**
170: * Get ComponentInfo for current Component Tree.<br/>
171: * it always return RootComponent's getComponentInfo(), if RootComponent doesn't exist return null;
172: */
173: protected ComponentInfo getComponentInfo() {
174: if (_rootcomp != null) {
175: return _rootcomp.getComponentInfo();
176: }
177: return null;
178: }
179:
180: /**
181: * Override Method, don't render children my self. return false;
182: */
183: public boolean getRendersChildren() {
184: if (isSuppressed()) {
185: return true;
186: } else {
187: return false;
188: }
189: }
190:
191: public void encodeChildren(FacesContext context) throws IOException {
192: if (!isRendered() || !isEffective())
193: return; //nothing to do
194: /*
195: * To get body content from children component in surpressed mode.
196: *
197: */
198: if (isSuppressed()) {
199: StringWriter sw = new StringWriter();
200:
201: ResponseWriter ow = context.getResponseWriter();
202:
203: context.setResponseWriter(ow.cloneWithWriter(sw));
204:
205: List children = this .getChildren();
206: for (Iterator iter = children.iterator(); iter.hasNext();) {
207:
208: Utils.renderComponent((UIComponent) iter.next(),
209: context);
210: }
211:
212: context.setResponseWriter(ow);
213: sw.close();
214: String content = sw.toString();
215:
216: sw = null;
217: if (this .getBodyContent() != null
218: && !"".equals(this .getBodyContent())) {
219: content += this .getBodyContent();
220: }
221: this .setBodyContent(content);
222:
223: }
224: }
225:
226: /**
227: * Override Method, write a special mark denoting the component in this method
228: */
229: public void encodeEnd(FacesContext context) throws IOException {
230:
231: if (!isRendered() || !isEffective())
232: return; //nothing to do
233: /*
234: * UIComponentTagBase is the base class for all JSP tags that use
235: * the "classic" JSP tag interface that correspond to a UIComponent instance in the view.
236: * In Faces 1.2, all component tags are BodyTag instances to allow for the execution of the page to build the component tree,
237: * but not render it. Rendering happens only after the component tree is completely built.
238: *
239: * In UIComponentClassicTagBase doStartTag phrase, inline text which appears before tag will be generated to
240: * a verbatim and attached to its parent. Then, it will be cleared. In order to adapt JSF 1.2,
241: * a temporary StringWriter is needed for holding the content and assigns the content to bodyContent in
242: * encodeEnd()
243: */
244: context.setResponseWriter(fakeOw);
245: fakeSw.close();
246: String content = fakeSw.toString();
247: if (this .getBodyContent() != null) {
248: content = fakeSw.toString() + this .getBodyContent();
249: }
250:
251: fakeSw = null;
252: this .setBodyContent(content);
253:
254: ResponseWriter rw = context.getResponseWriter();
255: if (isSuppressed()) {
256:
257: //TODO implement it.
258: //throw new UnsupportedOperationException("don't put component inside a suppressed parent component "+this.getParent()+", This feature doen't implement yet");
259: StringWriter writer = new StringWriter();
260: writeComponentMark(writer);
261: /*if(_parentcomp!=null){
262: _parentcomp.setBodyContent(_suppressedContent+writer.toString());
263: }else{
264: _rootcomp.setBodyContent(_suppressedContent+writer.toString());
265: }*/
266: rw.write(writer.toString());
267: } else {
268: writeComponentMark(rw); //write a special mark denoting the component
269: }
270:
271: }
272:
273: /**
274: * Call by RootComponent or BranchComponent to load zk stuff.
275: */
276: protected void loadZULTree(org.zkoss.zk.ui.Page page,
277: StringWriter writer) throws IOException {
278: if (!isRendered() || !isEffective())
279: return; //nothing to do
280: _composer = new ComposerHandler(_compAttrMap.get("apply"));
281: initComponent(page);
282: afterComposeComponent();//finish compose the component
283: _composer = null;
284: setBodyContent(null); //clear
285: }
286:
287: /**
288: * Create ZUL Component, and then associate it to it's Parent Component or Page Component
289: * Called by {@link #loadZULTree}.
290: */
291: /*package*/void initComponent(org.zkoss.zk.ui.Page page) {
292:
293: if (_rootcomp == null)
294: throw new IllegalStateException(
295: "Must be nested inside the page component: " + this );
296:
297: //maybe user binding this ZULJSF Component in session , so clear associated zulcomp before init
298: if (_zulcomp != null) {
299: _zulcomp.detach();
300: _zulcomp = null;
301: }
302:
303: try {
304: String compName = getComponentDefName();
305: ComponentDefinition compdef = page.getComponentDefinition(
306: compName, true);
307: if (compdef == null) {
308: throw new RuntimeException(
309: "Component Definition not found :" + compName);
310: }
311: //TODO: composer.doBeforeCompose(page, parentComponent, compInfo);
312: _zulcomp = (Component) compdef.resolveImplementationClass(
313: page, getUse()).newInstance();
314: //apply definition
315: _zulcomp.getDefinition().applyProperties(_zulcomp);
316:
317: _composer.doBeforeComposeChildren(_zulcomp);
318: } catch (Exception e) {
319: try {
320: _composer.doCatch(e);
321: } catch (Exception e1) {
322: StackTraceElement[] oriArr = e.getStackTrace();
323: StackTraceElement[] erArr = e1.getStackTrace();
324: StackTraceElement[] newErrArr = new StackTraceElement[oriArr.length
325: + erArr.length];
326: System
327: .arraycopy(newErrArr, 0, oriArr, 0,
328: oriArr.length);
329: System.arraycopy(newErrArr, oriArr.length, erArr, 0,
330: erArr.length);
331: e.setStackTrace(newErrArr);
332: }
333: throw new RuntimeException(e);
334: } finally {
335: try {
336: _composer.doFinally();
337: } catch (Exception e) {
338: throw new RuntimeException(e);
339: }
340: }
341:
342: if (_idSet) {
343: _zulcomp.setId(getId());
344: }
345:
346: if (_parentcomp != null) {
347: _parentcomp.addChildZULComponent(this );
348: } else {
349: _rootcomp.addChildZULComponent(this );
350: }
351:
352: }
353:
354: /**
355: * Returns the ZUL Component associated with this ZUL JSF Component
356: * the associated ZUL Component is only exist after {@link #loadZULTree}
357: */
358: protected Component getZULComponent() {
359: return _zulcomp;
360: }
361:
362: /** after children creation do dynamic attributes setter work and registers event handler.
363: * Called by {@link #loadZULTree}.
364: */
365: /*package*/void afterComposeComponent() {
366:
367: if (_zulcomp == null)
368: throw new RuntimeException("no zul component be created.");
369:
370: //map for zul component attribute
371: Map zulAttrMap = new LinkedHashMap();
372: //map for zul component listener
373: Map eventListenerMap = new LinkedHashMap();
374:
375: //setup EventListener and zul attribute from component attribute;
376: if (_compAttrMap != null) {
377: Iterator iter = _compAttrMap.keySet().iterator();
378: while (iter.hasNext()) {
379: String localName = (String) iter.next();
380: Object value = _compAttrMap.get(localName);
381: if (localName.startsWith("on")) {
382: eventListenerMap.put(localName, value);
383: } else {
384: zulAttrMap.put(localName, value);
385: }
386: }
387: }
388:
389: try {
390: evaluateDynaAttributes(_zulcomp, zulAttrMap);
391: } catch (Exception e) {
392: throw new RuntimeException(e);
393: }
394:
395: //process the forward condition
396: if (_forward != null) {
397: ComponentsCtrl.applyForward(_zulcomp, _forward);
398: }
399:
400: //add event handle ...
401: for (Iterator itor = eventListenerMap.entrySet().iterator(); itor
402: .hasNext();) {
403: Map.Entry entry = (Map.Entry) itor.next();
404: final ZScript zscript = ZScript.parseContent((String) entry
405: .getValue());
406: ((ComponentCtrl) _zulcomp).addEventHandler((String) entry
407: .getKey(), new EventHandler(zscript, null));
408: }
409:
410: //do afterCompose
411: if (_zulcomp instanceof AfterCompose)
412: ((AfterCompose) _zulcomp).afterCompose();
413:
414: if (_composer != null) {
415: try {
416: _composer.doAfterCompose(_zulcomp);
417: } catch (Exception e) {
418: throw new RuntimeException(e);
419: }
420: }
421:
422: //fire onCreate
423: if (Events.isListened(_zulcomp, Events.ON_CREATE, false))
424: Events.postEvent(new CreateEvent(Events.ON_CREATE,
425: _zulcomp, Executions.getCurrent().getArg()));
426:
427: //process JSF attribute after dynamic attribute is assigned.
428: afterZULComponentComposed(_zulcomp);
429: }
430:
431: /**
432: * Test if the attributes are annotation or component attributes.<br>
433: * If is Component attributes then invokes setter methods to update all assigned attributes.
434: * If is annotations then add annotation to component
435: * @param target the target component
436: * @throws NoSuchMethodException
437: * @throws ModificationException
438: */
439: protected void evaluateDynaAttributes(final Component target,
440: Map zulAttrMap) throws ModificationException,
441: NoSuchMethodException {
442:
443: AnnotationHelper helper = null;
444: boolean hitann = false;
445: for (Iterator itor = zulAttrMap.entrySet().iterator(); itor
446: .hasNext();) {
447:
448: Map.Entry entry = (Entry) itor.next();
449:
450: String attnm = (String) entry.getKey();
451: Object value = zulAttrMap.get(attnm);
452: if (value instanceof ValueBinding) {
453: value = ((ValueBinding) value).getValue(this
454: .getFacesContext());
455: }
456:
457: hitann = false;
458:
459: if (value instanceof String) {
460: String attval = (String) value;
461: final int len = attval.length();
462: if (len >= 3 && attval.charAt(0) == '@'
463: && attval.charAt(1) == '{'
464: && attval.charAt(len - 1) == '}') { //annotation
465:
466: if (helper == null) {
467: helper = new AnnotationHelper();
468: }
469: helper.addByCompoundValue(attval.substring(2,
470: len - 1));
471: helper.applyAnnotations(target, "self"
472: .equals(attnm) ? null : attnm, true);
473: hitann = true;
474: }
475: }
476:
477: if (!hitann && !isZULLifeCycleAttribute(attnm)) {
478: if (target instanceof DynamicPropertied) {
479: System.out.println("set dynamic:" + attnm + ","
480: + value);
481: ((DynamicPropertied) target).setDynamicProperty(
482: attnm, value);
483: } else {
484: Fields.setField(target, attnm, value, true);
485: }
486: }
487: }
488:
489: if (_compCustomAttributes != null) {
490: for (Iterator iter = _compCustomAttributes.keySet()
491: .iterator(); iter.hasNext();) {
492: String att = (String) iter.next();
493: Object value = _compCustomAttributes.get(att);
494: target.setAttribute(att, value);
495: }
496: }
497: }
498:
499: /**
500: * return the life cycle only dynamic attribute, these attribute should not set to component
501: */
502: private boolean isZULLifeCycleAttribute(String attnm) {
503: if ("apply".equals(attnm))
504: return true;
505: return false;
506: }
507:
508: /**
509: * This method give the delivering class a chance to handle ZUL Component just after it is composed.<br/>
510: * This method will be invoked after ZUL component is composed and listener, attribute of ZUL component is setted.
511: * Note: Default implementation do nothing.
512: */
513: protected void afterZULComponentComposed(Component zulcomp) {
514: //do nothing.
515: }
516:
517: /** Writes a special mark to the output to denote the location
518: * of this component.
519: */
520: /*package*/void writeComponentMark(Writer writer)
521: throws IOException {
522: if (!isRendered() || !isEffective())
523: return; //nothing to do
524: Utils.writeComponentMark(writer, this );
525: }
526:
527: /**
528: * Get value of attribute of Component. if value of attribute is a ValueBinding, then Binding result will return.
529: * @param att attribute name
530: * @return value of attribute
531: */
532: public Object getAttributeValue(String att) {
533: Object value = _compAttrMap.get(att);
534: if (value instanceof ValueBinding) {
535: value = ((ValueBinding) value).getValue(this
536: .getFacesContext());
537: }
538: return value;
539: }
540:
541: /**
542: * set value to attribute of Component, if a ValueBinding is associated with attribute , then it will set value to Binding Object<br/>
543: *
544: * Note: set attribute after {@link #loadZULTree} doesn't affect the ZUL Component which is associated to this component.
545: *
546: * @param att attribute name
547: * @param value attribute value;
548: */
549: public void setAttributeValue(String att, Object value) {
550: Object oldvalue = _compAttrMap.get(att);
551: if (oldvalue instanceof ValueBinding) {
552: ((ValueBinding) value).setValue(this .getFacesContext(),
553: value);
554: } else {
555: _compAttrMap.put(att, value);
556: }
557: }
558:
559: /**
560: * Returns the class name that is used to implement the ZUL Component
561: * associated with this ZULJSF Component.
562: *
563: * <p>Default: null
564: *
565: * @return the class name used to implement the ZUL Component, or null
566: * to use the default
567: */
568: public String getUse() {
569: return _use;
570: }
571:
572: /**
573: * Sets the class name that is used to implement the ZUL Component
574: * associated with this ZULJSF Component.
575: *
576: * @param use the class name used to implement the ZUL Component, or null
577: * to use the default
578: */
579: public void setUse(String use) {
580: this ._use = use;
581: }
582:
583: /**
584: * Set dynamic Attribute of this ZULJSF Component,
585: * This method is called by LeafTag only, developer should not call this method directly.
586: *
587: * @param map the dynamic attributes.
588: */
589: public void setZULDynamicAttribute(Map map) {
590: _compAttrMap = map;
591: }
592:
593: /**
594: * set custom attributes of this ZULJSF Component,
595: * This method is called by BaseCustomeAttribute only,
596: * @param map
597: */
598: /*package*/void setZULCustomAttribute(Map map) {
599: _compCustomAttributes = map;
600: }
601:
602: /**
603: * set the attribute(name) of this component to value, the difference between this method and {@link #setAttributeValue}
604: * is this method directly replace the attribute whether the attribute associate to a ValueBinding.
605: * Note: set attribute after {@link #loadZULTree(org.zkoss.zk.ui.Page, StringWriter)} doesn't affect the ZUL Component which is associated to this component.
606: * @param name attribute name of component
607: * @param value value of attribute
608: */
609: protected void addZULDynamicAttribute(String name, Object value) {
610: if (_compAttrMap != null) {
611: _compAttrMap.put(name, value);
612: } else {
613: throw new NullPointerException();
614: }
615: }
616:
617: // ----------------------------------------------------- StateHolder Methods
618:
619: /**
620: * Override Method, save the state of this component.
621: */
622: public Object saveState(FacesContext context) {
623: Object values[] = new Object[6];
624: values[0] = super .saveState(context);
625: values[1] = _use;
626: Object m[] = saveAttachedMapState(context, _compAttrMap);
627: values[2] = m[0];
628: values[3] = m[1];
629: values[4] = _idSet ? Boolean.TRUE : Boolean.FALSE;
630: values[5] = _forward;
631: return (values);
632:
633: }
634:
635: /**
636: * Override Method, restore the state of this component.
637: */
638: public void restoreState(FacesContext context, Object state) {
639: Object values[] = (Object[]) state;
640: super .restoreState(context, values[0]);
641: _use = (String) values[1];
642: _compAttrMap = (Map) restoreAttachedMapState(context,
643: values[2], values[3]);
644: _idSet = ((Boolean) values[4]).booleanValue();
645: _forward = (String) values[5];
646: }
647:
648: /**
649: * Override Method, remember the id is set.
650: */
651: public void setId(String id) {
652: super .setId(id);
653: _idSet = true;
654: }
655:
656: /** Returns the forward condition that controls how to forward
657: * an event, that is received by the component created
658: * by this info, to another component.
659: *
660: * <p>Default: null.
661: *
662: * <p>If not null, when the component created by this
663: * info receives the event specified in the forward condition,
664: * it will forward it to the target component, which is also
665: * specified in the forward condition.
666: *
667: * @see #setForward
668: */
669: public String getForward() {
670: return _forward;
671: }
672:
673: /** Sets the forward condition that controls when to forward
674: * an event receiving by this component to another component.
675: *
676: * <p>The basic format:<br/>
677: * <code>onEvent1=id1/id2.onEvent2</code>
678: *
679: * <p>It means when onEvent1 is received, onEvent2 will be posted
680: * to the component with the specified path (id1/id2).
681: *
682: * <p>If onEvent1 is omitted, it is assumed to be onClick (and
683: * the equal sign need not to be specified.
684: * If the path is omitted, it is assumed to be the space owner
685: * {@link Component#getSpaceOwner}.
686: *
687: * <p>For example, "onOK" means "onClick=onOK".
688: *
689: * <p>You can specify several forward conditions by separating
690: * them with comma as follows:
691: *
692: * <p><code>onChanging=onChanging,onChange=onUpdate,onOK</code>
693: *
694: * @param forward the forward condition. There are several forms:
695: * "onEvent1", "target.onEvent1" and "onEvent1(target.onEvent2)",
696: * where target could be "id", "id1/id2" or "${elExpr}".
697: * The EL expression must return either a path or a reference to
698: * a component.
699: */
700: public void setForward(String forward) {
701: _forward = forward != null && forward.length() > 0 ? forward
702: : null;
703: }
704:
705: /**
706: * get parent UI Form of this component.
707: * @return the parent component which type is UIForm.
708: */
709: /*package*/UIForm getUIForm() {
710: UIComponent comp = getParent();
711: while (comp != null) {
712: if (comp instanceof UIForm) {
713: return (UIForm) comp;
714: }
715: comp = comp.getParent();
716: }
717: return null;
718: }
719:
720: }
|