/* GenericAutowireComposer.java
Purpose:
Description:
History:
Jun 11, 2008 10:56:06 AM, Created by henrichen
Copyright (C) 2008 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.util;
import java.lang.reflect.Method;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Library;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.IdSpace;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WebApp;
import org.zkoss.zk.ui.event.CreateEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SerializableEventListener;
/**
* <p>A skeletal composer that you can extend and write intuitive onXxx
* event handler methods with "auto-wired" accessible variable objects such
* as implicit objects, components, and external resolvable variables in a ZK
* zuml page. This class will registers onXxx events to the supervised
* component and wire all accessible variable objects to this composer by
* calling setXxx() method or set xxx field value directly per the variable
* name. Since 3.0.7, this composer has wired all implicit objects
* such as self, spaceOwner, page, desktop, session, application,
* componentScope, spaceScope, pageScope, desktopScope, sessionScope,
* applicationScope, and requestScope, so you can use them directly. Besides
* that, it also provides alert(String message) method, so you can call alert()
* without problems. Since 3.5.2, the composer itself would be assigned as an
* attribute of the supervised component per the naming convention of
* the component id and composer class name or of component id and "composer".
* e.g. If the component id is "mywin" and the composer class is org.zkoss.MyComposer,
* then the composer can be referenced by the variable name of "mywin$MyController" or
* "mywin$composer". Notice that the '$' separator can be changed to other character
* such as '_' for Groovy or other environment that '$' is not applicable. Simply
* extends this class and calling {@link #GenericAutowireComposer(char separator)}
* constructor with proper separator character.</p>
*
* <P>Alternatives: in most cases, you don't extend from {@link GenericAutowireComposer} directly.
* Rather, you can extend from one of the following skeletons.
* <dl>
* <dt>{@link org.zkoss.zk.ui.select.SelectorComposer}</dt>
* <dd>It supports the autowiring based on Java annotation and a CSS3-based selector.
* If you don't know which one to use, use {@link org.zkoss.zk.ui.select.SelectorComposer}.</dd>
* <dt>{@link GenericForwardComposer}</dt>
* <dd>It supports the autowiring based on naming convention.
* You don't need to specify annotations explicitly, but it is error-prone if
* it is used properly.</dd>
* </dl>
*
* <p>Notice that since this composer kept references to the components, single
* instance composer object cannot be shared by multiple components.</p>
*
* <p>The following is an example. The onOK event listener is registered into
* the target window, and the Textbox component with id name "mytextbox" is
* injected into the "mytextbox" field automatically (so you can use
* mytextbox variable directly in onOK). The "value" property of "mytextbox"
* is assigned with composer's getTitle(), i.e. "ZK".</p>
*
* <pre><code>
* MyComposer.java
*
* public class MyComposer extends GenericAutowireComposer {
* private Textbox mytextbox;
*
* public void onOK() {
* mytextbox.setValue("Enter Pressed");
* alert("Hi!");
* }
* public String getTitle() {
* return "ZK";
* }
* }
*
* test.zul
*
* <window id="mywin" apply="MyComposer">
* <textbox id="mytextbox" value="${mywin$composer.title}"/>
* </window>
* </code></pre>
*
* <p>Since 5.0.8, you could name the composer by specify a custom attribute
* called <code>composerName</code>. For example,
*<pre><code>
* <window apply="MyComposer">
* <custom-attribute composerName="mc"/>
* <textbox id="mytextbox" value="${mc.title}"/>
* </window>
* </code></pre>
*
* @author henrichen
* @since 3.0.6
* @see ConventionWires
*/
public abstract class GenericAutowireComposer<T extends Component> extends GenericComposer<T>
implements ComponentCloneListener, ComponentActivationListener {
private static final long serialVersionUID = 20091006115726L;
private static final String COMPOSER_CLONE = "COMPOSER_CLONE";
private static final String ON_CLONE_DO_AFTER_COMPOSE = "onCLONE_DO_AFTER_COMPOSE";
private static Logger log = LoggerFactory.getLogger(GenericAutowireComposer.class);
/** Implicit Object; the applied component itself.
* @since 3.0.7
*/
protected transient T self;
/** Implicit Object; the space owner of the applied component.
* @since 3.0.7
*/
protected transient IdSpace spaceOwner;
/** Implicit Object; the page.
* @since 3.0.7
*/
protected transient Page page;
/** Implicit Object; the desktop.
* @since 3.0.7
*/
protected transient Desktop desktop;
/** Implicit Object; the session.
* @since 3.0.7
*/
protected transient Session session;
/** Implicit Object; the web application.
* @since 3.0.7
*/
protected transient WebApp application;
/** Implicit Object; a map of attributes defined in the applied component.
* @since 3.0.7
*/
protected transient Map<String, Object> componentScope;
/** Implicit Object; a map of attributes defined in the ID space contains the applied component.
* @since 3.0.7
*/
protected transient Map<String, Object> spaceScope;
/** Implicit Object; a map of attributes defined in the page.
* @since 3.0.7
*/
protected transient Map<String, Object> pageScope;
/** Implicit Object; a map of attributes defined in the desktop.
* @since 3.0.7
*/
protected transient Map<String, Object> desktopScope;
/** Implicit Object; a map of attributes defined in the session.
* @since 3.0.7
*/
protected transient Map<String, Object> sessionScope;
/** Implicit Object; a map of attributes defined in the web application.
* @since 3.0.7
*/
protected transient Map<String, Object> applicationScope;
/** Implicit Object; a map of attributes defined in the request.
* @since 3.0.7
*/
protected transient Map<String, Object> requestScope;
/** Implicit Object; the current execution.
* @since 3.0.7
*/
protected transient Execution execution;
/** Implicit Object; the arg argument passed to the createComponents method. It is never null.
* @since 3.0.8
*/
protected transient Map<?, ?> arg;
/** Implicit Object; the param argument passed from the http request.
* @since 3.6.1
*/
protected transient Map<String, String[]> param;
/** The separator used to separate the component ID and event name.
* By default, it is '$'. For Groovy and other environment that '$'
* is not applicable, you can specify '_'.
*/
protected /*final*/ char _separator;
/** Indicates whether to ignore variables defined in zscript when wiring
* a member.
*/
private /*final*/ boolean _ignoreZScript; //don't make it final ZK Grails depends on it
/** Indicates whether to ignore variables defined in variable resolver
* ({@link Page#addVariableResolver}) when wiring a member.
*/
private /*final*/ boolean _ignoreXel;
/** The default constructor.
* <p>It is a shortcut of <code>GenericAutowireComposer('$',
* !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.zscript")),
* !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.xel")))</code>.
* <p>In other words, whether to ignore variables defined in ZSCRIPT and XEL depends
* on the library variables called <code>org.zkoss.zk.ui.composer.autowire.zscript</code>
* and <code>org.zkoss.zk.ui.composer.autowire.xel</code>.
* Furthermore, if not specified, their values are default to <b>false</b>, i.e.,
* they shall <t>not</t> be wired (i.e., shall be ignored)
* <p>If you want to control whether to wire ZSCRIPT's or XEL's variable
* explicitly, you could use
* {@link #GenericAutowireComposer(char,boolean,boolean)} instead.
*
* <h2>Version Difference</h2>
* <p>ZK 5.0 and earlier, this constructor is the same as
* <code>GenericAutowireComposer('$', false, false)</code><br/>
* In other words, it is default to wire (i.e., shall <i>not</i> ignore).
*/
protected GenericAutowireComposer() {
this('$');
}
/** Constructor with a custom separator.
* The separator is used to separate the component ID and event name.
* By default, it is '$'. For Groovy and other environment that '$'
* is not applicable, you can specify '_'.
* <p>It is a shortcut of <code>GenericAutowireComposer('$',
* !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.zscript")),
* !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.xel")))</code>.
* <p>In other words, whether to ignore variables defined in ZSCRIPT and XEL depends
* on the library variables called <code>org.zkoss.zk.ui.composer.autowire.zscript</code>
* and <code>org.zkoss.zk.ui.composer.autowire.xel</code>.
* Furthermore, if not specified, their values are default to <b>false</b>, i.e.,
* they shall <t>not</t> be wired (i.e., shall be ignored)
* <p>If you want to control whether to wire ZSCRIPT's or XEL's variable
* explicitly, you could use
* {@link #GenericAutowireComposer(char,boolean,boolean)} instead.
*
* <h2>Version Difference</h2>
* <p>ZK 5.0 and earlier, this constructor is the same as
* <code>GenericAutowireComposer('$', false, false)</code><br/>
* In other words, it is default to wire (i.e., shall <i>not</i> ignore).
* @param separator the separator used to separate the component ID and event name.
* Refer to {@link #_separator} for details.
* @since 3.6.0
*/
protected GenericAutowireComposer(char separator) {
initIgnores();
_separator = separator;
_ignoreZScript = _sIgnoreZScript;
_ignoreXel = _sIgnoreXel;
}
/** Constructors with full control, including separator, whether to
* search zscript and xel variables
* @param separator the separator used to separate the component ID and event name.
* Refer to {@link #_separator} for details.
* @param ignoreZScript whether to ignore variables defined in zscript when wiring
* a member.
* @param ignoreXel whether to ignore variables defined in variable resolver
* ({@link Page#addVariableResolver}) when wiring a member.
* @since 5.0.3
*/
protected GenericAutowireComposer(char separator, boolean ignoreZScript, boolean ignoreXel) {
_separator = separator;
_ignoreZScript = ignoreZScript;
_ignoreXel = ignoreXel;
}
private void initIgnores() {
if (!_sIgnoreChecked) {
_sIgnoreZScript = !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.zscript"));
_sIgnoreXel = !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.xel"));
_sIgnoreChecked = true;
}
}
private static boolean _sIgnoreChecked, _sIgnoreZScript, _sIgnoreXel;
/** Returns the current page.
* @since 5.0.10
*/
protected Page getPage() {
if (self != null) {
final Page page = self.getPage();
if (page != null)
return page;
}
return super.getPage();
}
/**
* Auto wire accessible variables of the specified component into a
* controller Java object; a subclass that
* override this method should remember to call super.doAfterCompose(comp)
* or it will not work.
*/
public void doAfterCompose(T comp) throws Exception {
super.doAfterCompose(comp);
//wire variables to reference fields (include implicit objects) ASAP
ConventionWires.wireVariables(comp, this, _separator, _ignoreZScript, _ignoreXel);
//register event to wire variables just before component onCreate
comp.addEventListener(1000, "onCreate", new BeforeCreateWireListener());
}
private class BeforeCreateWireListener implements SerializableEventListener<CreateEvent> {
public void onEvent(CreateEvent event) throws Exception {
//wire variables again so some late created object can be wired in(e.g. DataBinder)
ConventionWires.wireVariables(event.getTarget(), GenericAutowireComposer.this, _separator, _ignoreZScript,
_ignoreXel);
//called only once
event.getTarget().removeEventListener("onCreate", this);
}
}
/** Shortcut to call Messagebox.show(String).
* @since 3.0.7
*/
private static Method _alert;
protected void alert(String m) {
if ("ajax".equals(Executions.getCurrent().getDesktop().getDeviceType())) {
//zk.jar cannot depends on zul.jar; thus we call Messagebox.show() via
//reflection.
try {
if (_alert == null) {
final Class<?> mboxcls = Classes.forNameByThread("org.zkoss.zul.Messagebox");
_alert = mboxcls.getMethod("show", new Class<?>[] { String.class });
}
_alert.invoke(null, new Object[] { m });
return; //done
} catch (Throwable ex) {
log.debug("Failed to invoke org.zkoss.zul.Messagebox", ex);
//Ignore
}
}
org.zkoss.zk.ui.util.Clients.alert(m);
}
//ComponentCloneListener
/** Internal use only. Call-back method of CloneComposerListener. You shall
* not call this method directly. Clone this Composer when its applied
* component is cloned.
* @param comp the clone of the applied component
* @return A clone of this Composer.
* @since 3.5.2
*/
public Object willClone(Component comp) {
try {
final Execution exec = Executions.getCurrent();
final int idcode = System.identityHashCode(comp);
Composer composerClone = (Composer) exec.getAttribute(COMPOSER_CLONE + idcode);
if (composerClone == null) {
composerClone = (Composer) Classes.newInstance(getClass(), null);
exec.setAttribute(COMPOSER_CLONE + idcode, composerClone);
//cannot call doAfterCompose directly because the clone
//component might not be attach to Page yet
comp.addEventListener(ON_CLONE_DO_AFTER_COMPOSE, new CloneDoAfterCompose());
Events.postEvent(new Event(ON_CLONE_DO_AFTER_COMPOSE, comp, composerClone));
}
return composerClone;
} catch (Exception ex) {
throw UiException.Aide.wrap(ex);
}
}
//doAfterCompose, called once after clone
private static class CloneDoAfterCompose implements SerializableEventListener<Event> {
@SuppressWarnings("unchecked")
public void onEvent(Event event) throws Exception {
final Component clone = event.getTarget();
final GenericAutowireComposer composerClone = (GenericAutowireComposer) event.getData();
composerClone.doAfterCompose(clone);
clone.removeEventListener(ON_CLONE_DO_AFTER_COMPOSE, this);
}
}
//ComponentActivationListener
public void didActivate(Component comp) {
//wire variables to reference fields (include implicit objects)
//Note: we have to check _applied because application might store
//the composer somewhere other than the original component
if (comp != null && Objects.equals(comp.getUuid(), _applied)) {
if (self == null) { //Bug #2873310. didActivate only once
ConventionWires.wireImplicit(comp, this); //Bug ZK-546. Shall re-wire transient implicit variables only
}
}
//feature #ZK-2822
ConventionWires.wireServiceCommand(comp, this);
}
public void willPassivate(Component comp) {
}
}