0001: /* UiEngineImpl.java
0002:
0003: {{IS_NOTE
0004: Purpose:
0005:
0006: Description:
0007:
0008: History:
0009: Thu Jun 9 13:05:28 2005, Created by tomyeh
0010: }}IS_NOTE
0011:
0012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
0013:
0014: {{IS_RIGHT
0015: This program is distributed under GPL Version 2.0 in the hope that
0016: it will be useful, but WITHOUT ANY WARRANTY.
0017: }}IS_RIGHT
0018: */
0019: package org.zkoss.zk.ui.impl;
0020:
0021: import java.util.Iterator;
0022: import java.util.Map;
0023: import java.util.HashMap;
0024: import java.util.LinkedHashMap;
0025: import java.util.IdentityHashMap;
0026: import java.util.Set;
0027: import java.util.HashSet;
0028: import java.util.List;
0029: import java.util.LinkedList;
0030: import java.util.Collections;
0031: import java.util.Collection;
0032: import java.io.Writer;
0033: import java.io.IOException;
0034:
0035: import org.zkoss.lang.D;
0036: import org.zkoss.lang.Classes;
0037: import org.zkoss.lang.Objects;
0038: import org.zkoss.lang.Threads;
0039: import org.zkoss.lang.Exceptions;
0040: import org.zkoss.lang.Expectable;
0041: import org.zkoss.mesg.Messages;
0042: import org.zkoss.util.logging.Log;
0043:
0044: import org.zkoss.zk.mesg.MZk;
0045: import org.zkoss.zk.ui.*;
0046: import org.zkoss.zk.ui.sys.*;
0047: import org.zkoss.zk.ui.event.*;
0048: import org.zkoss.zk.ui.metainfo.*;
0049: import org.zkoss.zk.ui.ext.AfterCompose;
0050: import org.zkoss.zk.ui.ext.Native;
0051: import org.zkoss.zk.ui.util.*;
0052: import org.zkoss.zk.scripting.*;
0053: import org.zkoss.zk.au.*;
0054: import org.zkoss.zk.au.out.*;
0055:
0056: /**
0057: * An implementation of {@link UiEngine}.
0058: *
0059: * @author tomyeh
0060: */
0061: public class UiEngineImpl implements UiEngine {
0062: private static final Log log = Log.lookup(UiEngineImpl.class);
0063:
0064: /** The Web application this engine belongs to. */
0065: private WebApp _wapp;
0066: /** A pool of idle EventProcessingThreadImpl. */
0067: private final List _idles = new LinkedList();
0068: /** A map of suspended processing:
0069: * (Desktop desktop, IdentityHashMap(Object mutex, List(EventProcessingThreadImpl)).
0070: */
0071: private final Map _suspended = new HashMap();
0072: /** A map of resumed processing
0073: * (Desktop desktop, List(EventProcessingThreadImpl)).
0074: */
0075: private final Map _resumed = new HashMap();
0076: /** # of suspended event processing threads.
0077: */
0078: private int _suspCnt;
0079:
0080: public UiEngineImpl() {
0081: }
0082:
0083: //-- UiEngine --//
0084: public void start(WebApp wapp) {
0085: _wapp = wapp;
0086: }
0087:
0088: public void stop(WebApp wapp) {
0089: synchronized (_idles) {
0090: for (Iterator it = _idles.iterator(); it.hasNext();)
0091: ((EventProcessingThreadImpl) it.next())
0092: .cease("Stop application");
0093: _idles.clear();
0094: }
0095:
0096: synchronized (_suspended) {
0097: for (Iterator it = _suspended.values().iterator(); it
0098: .hasNext();) {
0099: final Map map = (Map) it.next();
0100: synchronized (map) {
0101: for (Iterator i2 = map.values().iterator(); i2
0102: .hasNext();) {
0103: final List list = (List) i2.next();
0104: for (Iterator i3 = list.iterator(); i3
0105: .hasNext();)
0106: ((EventProcessingThreadImpl) i3.next())
0107: .cease("Stop application");
0108: }
0109: }
0110: }
0111: _suspended.clear();
0112: }
0113: synchronized (_resumed) {
0114: for (Iterator it = _resumed.values().iterator(); it
0115: .hasNext();) {
0116: final List list = (List) it.next();
0117: synchronized (list) {
0118: for (Iterator i2 = list.iterator(); i2.hasNext();)
0119: ((EventProcessingThreadImpl) i2.next())
0120: .cease("Stop application");
0121: }
0122: }
0123: _resumed.clear();
0124: }
0125: }
0126:
0127: public boolean hasSuspendedThread() {
0128: if (!_suspended.isEmpty()) {
0129: synchronized (_suspended) {
0130: for (Iterator it = _suspended.values().iterator(); it
0131: .hasNext();) {
0132: final Map map = (Map) it.next();
0133: if (!map.isEmpty())
0134: return true;
0135: }
0136: }
0137: }
0138: return false;
0139: }
0140:
0141: public Collection getSuspendedThreads(Desktop desktop) {
0142: final Map map;
0143: synchronized (_suspended) {
0144: map = (Map) _suspended.get(desktop);
0145: }
0146: return map == null || map.isEmpty() ? Collections.EMPTY_LIST
0147: : Collections.synchronizedMap(map).values();
0148: }
0149:
0150: public boolean ceaseSuspendedThread(Desktop desktop,
0151: EventProcessingThread evtthd, String cause) {
0152: final Map map;
0153: synchronized (_suspended) {
0154: map = (Map) _suspended.get(desktop);
0155: }
0156: if (map == null)
0157: return false;
0158:
0159: boolean found = false;
0160: synchronized (map) {
0161: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
0162: final Map.Entry me = (Map.Entry) it.next();
0163: final List list = (List) me.getValue();
0164: found = list.remove(evtthd); //found
0165: if (found) {
0166: if (list.isEmpty())
0167: it.remove(); //(mutex, list) no longer useful
0168: break; //DONE
0169: }
0170: }
0171: }
0172: if (found)
0173: ((EventProcessingThreadImpl) evtthd).cease(cause);
0174: return found;
0175: }
0176:
0177: public void desktopDestroyed(Desktop desktop) {
0178: // if (log.debugable()) log.debug("destroy "+desktop);
0179:
0180: final Configuration conf = _wapp.getConfiguration();
0181: final Map map;
0182: synchronized (_suspended) {
0183: map = (Map) _suspended.remove(desktop);
0184: }
0185: if (map != null) {
0186: synchronized (map) {
0187: for (Iterator it = map.values().iterator(); it
0188: .hasNext();) {
0189: final List list = (List) it.next();
0190: for (Iterator i2 = list.iterator(); i2.hasNext();) {
0191: final EventProcessingThreadImpl evtthd = (EventProcessingThreadImpl) i2
0192: .next();
0193: evtthd.ceaseSilently("Destroy desktop "
0194: + desktop);
0195: conf.invokeEventThreadResumeAborts(evtthd
0196: .getComponent(), evtthd.getEvent());
0197: }
0198: }
0199: }
0200: }
0201:
0202: final List list;
0203: synchronized (_resumed) {
0204: list = (List) _resumed.remove(desktop);
0205: }
0206: if (list != null) {
0207: synchronized (list) {
0208: for (Iterator it = list.iterator(); it.hasNext();) {
0209: final EventProcessingThreadImpl evtthd = (EventProcessingThreadImpl) it
0210: .next();
0211: evtthd.ceaseSilently("Destroy desktop " + desktop);
0212: conf.invokeEventThreadResumeAborts(evtthd
0213: .getComponent(), evtthd.getEvent());
0214: }
0215: }
0216: }
0217:
0218: ((DesktopCtrl) desktop).destroy();
0219: }
0220:
0221: private static UiVisualizer getCurrentVisualizer() {
0222: final ExecutionCtrl execCtrl = ExecutionsCtrl.getCurrentCtrl();
0223: if (execCtrl == null)
0224: throw new IllegalStateException(
0225: "Components can be accessed only in event listeners");
0226: return (UiVisualizer) execCtrl.getVisualizer();
0227: }
0228:
0229: public void pushOwner(Component comp) {
0230: getCurrentVisualizer().pushOwner(comp);
0231: }
0232:
0233: public void popOwner() {
0234: getCurrentVisualizer().popOwner();
0235: }
0236:
0237: public void addInvalidate(Page page) {
0238: if (page == null)
0239: throw new IllegalArgumentException();
0240: getCurrentVisualizer().addInvalidate(page);
0241: }
0242:
0243: public void addInvalidate(Component comp) {
0244: if (comp == null)
0245: throw new IllegalArgumentException();
0246: getCurrentVisualizer().addInvalidate(comp);
0247: }
0248:
0249: public void addSmartUpdate(Component comp, String attr, String value) {
0250: if (comp == null)
0251: throw new IllegalArgumentException();
0252: getCurrentVisualizer().addSmartUpdate(comp, attr, value);
0253: }
0254:
0255: public void addSmartUpdate(Component comp, String attr,
0256: DeferredValue value) {
0257: if (comp == null)
0258: throw new IllegalArgumentException();
0259: getCurrentVisualizer().addSmartUpdate(comp, attr, value);
0260: }
0261:
0262: public void addResponse(String key, AuResponse response) {
0263: getCurrentVisualizer().addResponse(key, response);
0264: }
0265:
0266: public void addMoved(Component comp, Component oldparent,
0267: Page oldpg, Page newpg) {
0268: if (comp == null)
0269: throw new IllegalArgumentException();
0270: getCurrentVisualizer().addMoved(comp, oldparent, oldpg, newpg);
0271: }
0272:
0273: /** Called before changing the component's UUID.
0274: *
0275: * @param addOnlyMoved if true, it is added only if it was moved
0276: * before (see {@link #addMoved}).
0277: */
0278: public void addUuidChanged(Component comp, boolean addOnlyMoved) {
0279: if (comp == null)
0280: throw new IllegalArgumentException();
0281: getCurrentVisualizer().addUuidChanged(comp, addOnlyMoved);
0282: }
0283:
0284: //-- Creating a new page --//
0285: public void execNewPage(Execution exec, Richlet richlet, Page page,
0286: Writer out) throws IOException {
0287: execNewPage0(exec, null, richlet, page, out);
0288: }
0289:
0290: public void execNewPage(Execution exec, PageDefinition pagedef,
0291: Page page, Writer out) throws IOException {
0292: execNewPage0(exec, pagedef, null, page, out);
0293: }
0294:
0295: /** It assumes exactly one of pagedef and richlet is not null.
0296: */
0297: public void execNewPage0(final Execution exec,
0298: final PageDefinition pagedef, final Richlet richlet,
0299: final Page page, final Writer out) throws IOException {
0300: //Update the device type first. If this is the second page and not
0301: //belonging to the same device type, an exception is thrown
0302: final Desktop desktop = exec.getDesktop();
0303: final LanguageDefinition langdef = //default page
0304: pagedef != null ? pagedef.getLanguageDefinition()
0305: : richlet != null ? richlet.getLanguageDefinition()
0306: : null;
0307: if (langdef != null)
0308: desktop.setDeviceType(langdef.getDeviceType()); //set and check!
0309:
0310: //It is possible this method is invoked when processing other exec
0311: final Execution oldexec = Executions.getCurrent();
0312: final ExecutionCtrl oldexecCtrl = (ExecutionCtrl) oldexec;
0313: final UiVisualizer olduv = oldexecCtrl != null ? (UiVisualizer) oldexecCtrl
0314: .getVisualizer()
0315: : null;
0316:
0317: final UiVisualizer uv;
0318: if (olduv != null)
0319: uv = doReactivate(exec, olduv);
0320: else
0321: uv = doActivate(exec, null, false);
0322:
0323: final ExecutionCtrl execCtrl = (ExecutionCtrl) exec;
0324: final Page old = execCtrl.getCurrentPage();
0325: final PageDefinition olddef = execCtrl
0326: .getCurrentPageDefinition();
0327: execCtrl.setCurrentPage(page);
0328: execCtrl.setCurrentPageDefinition(pagedef);
0329:
0330: final Configuration config = _wapp.getConfiguration();
0331: AbortingReason abrn = null;
0332: boolean cleaned = false;
0333: try {
0334: config.invokeExecutionInits(exec, oldexec);
0335:
0336: if (olduv != null) {
0337: final Component owner = olduv.getOwner();
0338: if (owner != null) {
0339: ((PageCtrl) page).setOwner(owner);
0340: // if (D.ON && log.finerable()) log.finer("Set owner of "+page+" to "+owner);
0341: }
0342: }
0343:
0344: //Cycle 1: Creates all components
0345:
0346: //Note:
0347: //1) stylesheet, tablib are inited in Page's contructor
0348: //2) we add variable resolvers before init because
0349: //init's zscirpt might depend on it.
0350: if (pagedef != null) {
0351: pagedef.initXelContext(page);
0352:
0353: final Initiators inits = Initiators.doInit(pagedef,
0354: page);
0355: try {
0356: //Request 1472813: sendRedirect in init; test: sendRedirectNow.zul
0357: pagedef.init(page, !uv.isEverAsyncUpdate()
0358: && !uv.isAborting());
0359:
0360: final Component[] comps;
0361: final String uri = pagedef.getForwardURI(page);
0362: if (uri != null) {
0363: comps = new Component[0];
0364: exec.forward(uri);
0365: } else {
0366: comps = uv.isAborting() || exec.isVoided() ? null
0367: : execCreate(new CreateInfo(
0368: ((WebAppCtrl) _wapp)
0369: .getUiFactory(), exec,
0370: page), pagedef, null);
0371: }
0372:
0373: inits.doAfterCompose(page, comps);
0374: } catch (Throwable ex) {
0375: if (!inits.doCatch(ex))
0376: throw UiException.Aide.wrap(ex);
0377: } finally {
0378: inits.doFinally();
0379: }
0380: } else {
0381: //FUTURE: a way to allow richlet to set page ID
0382: ((PageCtrl) page).init(new PageConfig() {
0383: public String getId() {
0384: return null;
0385: }
0386:
0387: public String getUuid() {
0388: return null;
0389: }
0390:
0391: public String getTitle() {
0392: return null;
0393: }
0394:
0395: public String getStyle() {
0396: return null;
0397: }
0398:
0399: public String getHeaders() {
0400: return null;
0401: }
0402:
0403: public String getRootAttributes() {
0404: return null;
0405: }
0406:
0407: public String getContentType() {
0408: return null;
0409: }
0410:
0411: public String getDocType() {
0412: return null;
0413: }
0414:
0415: public String getFirstLine() {
0416: return null;
0417: }
0418:
0419: public Boolean getCacheable() {
0420: return null;
0421: }
0422: });
0423: richlet.service(page);
0424: }
0425: if (exec.isVoided())
0426: return; //don't generate any output
0427:
0428: //Cycle 2: process pending events
0429: //Unlike execUpdate, execution is aborted here if any exception
0430: Event event = nextEvent(uv);
0431: do {
0432: for (; event != null; event = nextEvent(uv))
0433: process(desktop, event);
0434: resumeAll(desktop, uv, null);
0435: } while ((event = nextEvent(uv)) != null);
0436:
0437: //Cycle 2a: Handle aborting reason
0438: abrn = uv.getAbortingReason();
0439: if (abrn != null)
0440: abrn.execute(); //always execute even if !isAborting
0441:
0442: //Cycle 3: Redraw the page (and responses)
0443: List responses = uv.getResponses();
0444:
0445: if (olduv != null && olduv.addToFirstAsyncUpdate(responses))
0446: responses = null;
0447: //A new ZK page might be included by an async update
0448: //(example: ZUL's include).
0449: //If so, we cannot generate the responses in the page.
0450: //Rather, we shall add them to the async update.
0451:
0452: ((PageCtrl) page).redraw(responses, out);
0453: } catch (Throwable ex) {
0454: cleaned = true;
0455: final List errs = new LinkedList();
0456: errs.add(ex);
0457: config.invokeExecutionCleanups(exec, oldexec, errs);
0458: //CONSIDER: whether to pass cleanup's error to users
0459:
0460: if (!errs.isEmpty()) {
0461: ex = (Throwable) errs.get(0);
0462: if (ex instanceof IOException)
0463: throw (IOException) ex;
0464: throw UiException.Aide.wrap(ex);
0465: }
0466: } finally {
0467: if (!cleaned)
0468: config.invokeExecutionCleanups(exec, oldexec, null);
0469: //CONSIDER: whether to pass cleanup's error to users
0470: if (abrn != null) {
0471: try {
0472: abrn.finish();
0473: } catch (Throwable t) {
0474: log.warning(t);
0475: }
0476: }
0477:
0478: execCtrl.setCurrentPage(old); //restore it
0479: execCtrl.setCurrentPageDefinition(olddef); //restore it
0480:
0481: if (olduv != null)
0482: doDereactivate(exec, olduv);
0483: else
0484: doDeactivate(exec);
0485: }
0486: }
0487:
0488: private static final Event nextEvent(UiVisualizer uv) {
0489: final Event evt = ((ExecutionCtrl) uv.getExecution())
0490: .getNextEvent();
0491: return evt != null && !uv.isAborting() ? evt : null;
0492: }
0493:
0494: /** Cycle 1:
0495: * Creates all child components defined in the specified definition.
0496: * @return the first component being created.
0497: */
0498: private static final Component[] execCreate(CreateInfo ci,
0499: NodeInfo parentInfo, Component parent) throws IOException {
0500: if (parentInfo instanceof ComponentInfo) {
0501: final ComponentInfo pi = (ComponentInfo) parentInfo;
0502: final String fulfill = pi.getFulfill();
0503: if (fulfill != null && fulfill.length() > 0) { //defer the creation of children
0504: new FulfillListener(fulfill, pi, parent);
0505: return new Component[0];
0506: }
0507: }
0508: return execCreate0(ci, parentInfo, parent);
0509: }
0510:
0511: private static final Component[] execCreate0(CreateInfo ci,
0512: NodeInfo parentInfo, Component parent) throws IOException {
0513: final List created = new LinkedList();
0514: final Page page = ci.page;
0515: final PageDefinition pagedef = parentInfo.getPageDefinition();
0516: //note: don't use page.getDefinition because createComponents
0517: //might be called from a page other than instance's
0518: for (Iterator it = parentInfo.getChildren().iterator(); it
0519: .hasNext();) {
0520: final Object meta = it.next();
0521: if (meta instanceof ComponentInfo) {
0522: final ComponentInfo childInfo = (ComponentInfo) meta;
0523: final ForEach forEach = childInfo.getForEach(page,
0524: parent);
0525: if (forEach == null) {
0526: if (isEffective(childInfo, page, parent)) {
0527: final Component[] children = execCreateChild(
0528: ci, parent, childInfo);
0529: for (int j = 0; j < children.length; ++j)
0530: created.add(children[j]);
0531: }
0532: } else {
0533: while (forEach.next()) {
0534: if (isEffective(childInfo, page, parent)) {
0535: final Component[] children = execCreateChild(
0536: ci, parent, childInfo);
0537: for (int j = 0; j < children.length; ++j)
0538: created.add(children[j]);
0539: }
0540: }
0541: }
0542: } else if (meta instanceof TextInfo) {
0543: //parent must be a native component
0544: final String s = ((TextInfo) meta).getValue(parent);
0545: if (s != null && s.length() > 0)
0546: parent.appendChild(((Native) parent).getHelper()
0547: .newNative(s));
0548: } else {
0549: execNonComponent(ci, parent, meta);
0550: }
0551: }
0552: return (Component[]) created.toArray(new Component[created
0553: .size()]);
0554: }
0555:
0556: private static Component[] execCreateChild(CreateInfo ci,
0557: Component parent, ComponentInfo childInfo)
0558: throws IOException {
0559: final ComponentDefinition childdef = childInfo
0560: .getComponentDefinition();
0561: if (ComponentDefinition.ZK == childdef) {
0562: return execCreate(ci, childInfo, parent);
0563: } else if (childdef.isInlineMacro()) {
0564: final Map props = new HashMap();
0565: props.put("includer", parent);
0566: childInfo.evalProperties(props, ci.page, parent, true);
0567: return new Component[] { ci.exec.createComponents(childdef
0568: .getMacroURI(), parent, props) };
0569: } else {
0570: final Component child = execCreateChild0(ci, parent,
0571: childInfo);
0572: return child != null ? new Component[] { child }
0573: : new Component[0];
0574: }
0575: }
0576:
0577: private static Component execCreateChild0(CreateInfo ci,
0578: Component parent, ComponentInfo childInfo)
0579: throws IOException {
0580: final Composer composer = childInfo
0581: .getComposer(ci.page, parent);
0582: final ComposerExt composerExt = composer instanceof ComposerExt ? (ComposerExt) composer
0583: : null;
0584: Component child = null;
0585: try {
0586: if (composerExt != null) {
0587: childInfo = composerExt.doBeforeCompose(ci.page,
0588: parent, childInfo);
0589: if (childInfo == null)
0590: return null;
0591: }
0592:
0593: child = ci.uf.newComponent(ci.page, parent, childInfo);
0594:
0595: final boolean bNative = childInfo instanceof NativeInfo;
0596: if (bNative)
0597: setProlog(ci, child, (NativeInfo) childInfo);
0598:
0599: if (composerExt != null)
0600: composerExt.doBeforeComposeChildren(child);
0601:
0602: execCreate(ci, childInfo, child); //recursive
0603:
0604: if (bNative)
0605: setEpilog(ci, child, (NativeInfo) childInfo);
0606:
0607: if (child instanceof AfterCompose)
0608: ((AfterCompose) child).afterCompose();
0609: if (composer != null)
0610: composer.doAfterCompose(child);
0611:
0612: ComponentsCtrl.applyForward(child, childInfo.getForward());
0613: //applies the forward condition
0614: //1) we did it after all child created, so it may reference
0615: //to it child (thought rarely happens)
0616: //2) we did it after afterCompose, so what specified
0617: //here has higher priority than class defined by app dev
0618:
0619: if (Events.isListened(child, Events.ON_CREATE, false))
0620: Events.postEvent(new CreateEvent(Events.ON_CREATE,
0621: child, ci.exec.getArg()));
0622:
0623: return child;
0624: } catch (Throwable ex) {
0625: boolean ignore = false;
0626: if (composerExt != null) {
0627: try {
0628: ignore = composerExt.doCatch(ex);
0629: } catch (Throwable t) {
0630: log.error("Failed to invoke doCatch for "
0631: + childInfo, t);
0632: }
0633: }
0634: if (!ignore)
0635: throw UiException.Aide.wrap(ex);
0636:
0637: return child != null && child.getPage() != null ? child
0638: : null;
0639: //return child only if attached successfully
0640: } finally {
0641: if (composerExt != null) {
0642: try {
0643: composerExt.doFinally();
0644: } catch (Throwable ex) {
0645: throw UiException.Aide.wrap(ex);
0646: }
0647: }
0648: }
0649: }
0650:
0651: /** Executes a non-component object, such as ZScript, AttributesInfo...
0652: */
0653: private static final void execNonComponent(CreateInfo ci,
0654: Component comp, Object meta) {
0655: final Page page = ci.page;
0656: if (meta instanceof ZScript) {
0657: final ZScript zscript = (ZScript) meta;
0658: if (zscript.isDeferred()) {
0659: ((PageCtrl) page).addDeferredZScript(comp, zscript);
0660: //isEffective is handled later
0661: } else if (isEffective(zscript, page, comp)) {
0662: final Map backup = new HashMap();
0663: final Namespace ns = comp != null ? Namespaces
0664: .beforeInterpret(backup, comp, false)
0665: : Namespaces.beforeInterpret(backup, page,
0666: false);
0667: try {
0668: page.interpret(zscript.getLanguage(), zscript
0669: .getContent(page, comp), ns);
0670: } finally {
0671: Namespaces.afterInterpret(backup, ns, false);
0672: }
0673: }
0674: } else if (meta instanceof AttributesInfo) {
0675: final AttributesInfo attrs = (AttributesInfo) meta;
0676: if (comp != null)
0677: attrs.apply(comp); //it handles isEffective
0678: else
0679: attrs.apply(page);
0680: } else if (meta instanceof VariablesInfo) {
0681: final VariablesInfo vars = (VariablesInfo) meta;
0682: if (comp != null)
0683: vars.apply(comp); //it handles isEffective
0684: else
0685: vars.apply(page);
0686: } else {
0687: throw new IllegalStateException("Unknown metainfo: " + meta);
0688: }
0689: }
0690:
0691: private static final boolean isEffective(Condition cond, Page page,
0692: Component comp) {
0693: return comp != null ? cond.isEffective(comp) : cond
0694: .isEffective(page);
0695: }
0696:
0697: public Component[] createComponents(Execution exec,
0698: PageDefinition pagedef, Page page, Component parent, Map arg) {
0699: if (pagedef == null)
0700: throw new IllegalArgumentException("pagedef");
0701: if (parent != null) {
0702: if (parent.getPage() != null)
0703: page = parent.getPage();
0704: } else if (page != null) {
0705: parent = ((PageCtrl) page).getDefaultParent();
0706: }
0707: if (page == null)
0708: page = getCurrentPage(exec);
0709:
0710: final ExecutionCtrl execCtrl = (ExecutionCtrl) exec;
0711: if (!execCtrl.isActivated())
0712: throw new IllegalStateException("Not activated yet");
0713:
0714: final Page old = execCtrl.getCurrentPage();
0715: final PageDefinition olddef = execCtrl
0716: .getCurrentPageDefinition();
0717: if (page != null && page != old)
0718: execCtrl.setCurrentPage(page);
0719: execCtrl.setCurrentPageDefinition(pagedef);
0720: exec.pushArg(arg != null ? arg : Collections.EMPTY_MAP);
0721:
0722: //Note: we add taglib, stylesheets and var-resolvers to the page
0723: //it might cause name pollution but we got no choice since they
0724: //are used as long as components created by this method are alive
0725: if (page != null)
0726: pagedef.initXelContext(page);
0727:
0728: //Note: the forward directives are ignore in this case
0729:
0730: final Initiators inits = Initiators.doInit(pagedef, page);
0731: try {
0732: final Component[] comps = execCreate(new CreateInfo(
0733: ((WebAppCtrl) exec.getDesktop().getWebApp())
0734: .getUiFactory(), exec, page), pagedef,
0735: parent);
0736: inits.doAfterCompose(page, comps);
0737: return comps;
0738: } catch (Throwable ex) {
0739: inits.doCatch(ex);
0740: throw UiException.Aide.wrap(ex);
0741: } finally {
0742: exec.popArg();
0743: execCtrl.setCurrentPage(old); //restore it
0744: execCtrl.setCurrentPageDefinition(olddef); //restore it
0745:
0746: inits.doFinally();
0747: }
0748: }
0749:
0750: public void sendRedirect(String uri, String target) {
0751: if (uri != null && uri.length() == 0)
0752: uri = null;
0753: final UiVisualizer uv = getCurrentVisualizer();
0754: uv.setAbortingReason(new AbortBySendRedirect(uri != null ? uv
0755: .getExecution().encodeURL(uri) : "", target));
0756: }
0757:
0758: public void setAbortingReason(AbortingReason aborting) {
0759: final UiVisualizer uv = getCurrentVisualizer();
0760: uv.setAbortingReason(aborting);
0761: }
0762:
0763: //-- Recovering desktop --//
0764: public void execRecover(Execution exec, FailoverManager failover) {
0765: final Desktop desktop = exec.getDesktop();
0766: final Session sess = desktop.getSession();
0767:
0768: doActivate(exec, null, true); //it must not return null
0769: try {
0770: failover.recover(sess, exec, desktop);
0771: } finally {
0772: doDeactivate(exec);
0773: }
0774: }
0775:
0776: //-- Asynchronous updates --//
0777: public void execUpdate(Execution exec, List requests, AuWriter out)
0778: throws IOException {
0779: execUpdate(exec, requests, null, out);
0780: }
0781:
0782: public Collection execUpdate(Execution exec, List requests,
0783: String reqId, AuWriter out) throws IOException {
0784: if (requests == null)
0785: throw new IllegalArgumentException("null requests");
0786: assert D.OFF || ExecutionsCtrl.getCurrentCtrl() == null : "Impossible to re-activate for update: old="
0787: + ExecutionsCtrl.getCurrentCtrl()
0788: + ", new="
0789: + exec
0790: + ", reqs=" + requests;
0791:
0792: final UiVisualizer uv = doActivate(exec, requests, false);
0793: if (uv == null)
0794: return null; //done (request is added to the exec currently activated)
0795:
0796: final Desktop desktop = exec.getDesktop();
0797: final Configuration config = desktop.getWebApp()
0798: .getConfiguration();
0799: final Monitor monitor = config.getMonitor();
0800: if (monitor != null) {
0801: try {
0802: monitor.beforeUpdate(desktop, requests);
0803: } catch (Throwable ex) {
0804: log.error(ex);
0805: }
0806: }
0807:
0808: Collection doneReqIds = null; //request IDs that have been processed
0809: AbortingReason abrn = null;
0810: boolean cleaned = false;
0811: try {
0812: config.invokeExecutionInits(exec, null);
0813: final RequestQueue rque = ((DesktopCtrl) desktop)
0814: .getRequestQueue();
0815: if (reqId != null)
0816: rque.addRequestId(reqId);
0817:
0818: final List errs = new LinkedList();
0819: final long tmexpired = System.currentTimeMillis()
0820: + config.getMaxProcessTime();
0821: //Tom Yeh: 20060120
0822: //Don't process all requests if this thread has processed
0823: //a while. Thus, user could see the response sooner.
0824: for (AuRequest request; System.currentTimeMillis() < tmexpired
0825: && (request = rque.nextRequest()) != null;) {
0826: //Cycle 1: Process one request
0827: //Don't process more such that requests will be queued
0828: //adn we have the chance to optimize them
0829: try {
0830: process(exec, request, !errs.isEmpty());
0831: } catch (ComponentNotFoundException ex) {
0832: //possible because the previous might remove some comp
0833: //so ignore it
0834: // if (log.finable()) log.fine("Component not found: "+request);
0835: } catch (Throwable ex) {
0836: handleError(ex, uv, errs);
0837: //we don't skip request to avoid mis-match between c/s
0838: }
0839:
0840: //Cycle 2: Process any pending events posted by components
0841: Event event = nextEvent(uv);
0842: do {
0843: for (; event != null; event = nextEvent(uv)) {
0844: try {
0845: process(desktop, event);
0846: } catch (Throwable ex) {
0847: handleError(ex, uv, errs);
0848: break; //skip the rest of events!
0849: }
0850: }
0851:
0852: resumeAll(desktop, uv, errs);
0853: } while ((event = nextEvent(uv)) != null);
0854: }
0855:
0856: //Cycle 2a: Handle aborting reason
0857: abrn = uv.getAbortingReason();
0858: if (abrn != null)
0859: abrn.execute(); //always execute even if !isAborting
0860:
0861: //Cycle 3: Generate output
0862: List responses;
0863: try {
0864: //Note: we have to call visualizeErrors before uv.getResponses,
0865: //since it might create/update components
0866: if (!errs.isEmpty())
0867: visualizeErrors(exec, uv, errs);
0868:
0869: responses = uv.getResponses();
0870: } catch (Throwable ex) {
0871: responses = new LinkedList();
0872: responses.add(new AuAlert(Exceptions.getMessage(ex)));
0873:
0874: log.error(ex);
0875: }
0876:
0877: if (rque.endWithRequest()) //stop accept another request
0878: responses.add(new AuEcho(desktop)); //ask client to echo if any pending
0879: else
0880: doneReqIds = rque.clearRequestIds();
0881:
0882: out.writeSequenceId(desktop);
0883: out.write(responses);
0884:
0885: // if (log.debugable())
0886: // if (responses.size() < 5 || log.finerable()) log.finer("Responses: "+responses);
0887: // else log.debug("Responses: "+responses.subList(0, 5)+"...");
0888:
0889: cleaned = true;
0890: config.invokeExecutionCleanups(exec, null, errs);
0891: } catch (Throwable ex) {
0892: if (!cleaned) {
0893: cleaned = true;
0894: final List errs = new LinkedList();
0895: errs.add(ex);
0896: config.invokeExecutionCleanups(exec, null, errs);
0897: ex = errs.isEmpty() ? null : (Throwable) errs.get(0);
0898: }
0899:
0900: if (ex != null) {
0901: if (ex instanceof IOException)
0902: throw (IOException) ex;
0903: throw UiException.Aide.wrap(ex);
0904: }
0905: } finally {
0906: if (!cleaned)
0907: config.invokeExecutionCleanups(exec, null, null);
0908:
0909: if (abrn != null) {
0910: try {
0911: abrn.finish();
0912: } catch (Throwable t) {
0913: log.warning(t);
0914: }
0915: }
0916:
0917: doDeactivate(exec);
0918:
0919: if (monitor != null) {
0920: try {
0921: monitor.afterUpdate(desktop);
0922: } catch (Throwable ex) {
0923: log.error(ex);
0924: }
0925: }
0926:
0927: return doneReqIds;
0928: }
0929: }
0930:
0931: /** Handles each error. The erros will be queued to the errs list
0932: * and processed later by {@link #visualizeErrors}.
0933: */
0934: private static final void handleError(Throwable ex,
0935: UiVisualizer uv, List errs) {
0936: final Throwable err = ex;
0937: final Throwable t = Exceptions.findCause(ex, Expectable.class);
0938: if (t == null) {
0939: log.realCauseBriefly(ex);
0940: } else {
0941: ex = t;
0942: if (log.debugable())
0943: log.debug(Exceptions.getRealCause(ex));
0944: }
0945:
0946: if (ex instanceof WrongValueException) {
0947: WrongValueException wve = (WrongValueException) ex;
0948: final Component comp = wve.getComponent();
0949: if (comp != null) {
0950: wve = ((ComponentCtrl) comp).onWrongValue(wve);
0951: if (wve != null)
0952: uv.addResponse("wrongValue", new AuWrongValue(comp,
0953: Exceptions.getMessage(wve)));
0954: return;
0955: }
0956: }
0957:
0958: errs.add(err);
0959: }
0960:
0961: /** Post-process the errors to represent them to the user.
0962: * Note: errs must be non-empty
0963: */
0964: private final void visualizeErrors(Execution exec, UiVisualizer uv,
0965: List errs) {
0966: final StringBuffer sb = new StringBuffer(128);
0967: for (Iterator it = errs.iterator(); it.hasNext();) {
0968: final Throwable t = (Throwable) it.next();
0969: if (sb.length() > 0)
0970: sb.append('\n');
0971: sb.append(Exceptions.getMessage(t));
0972: }
0973: final String msg = sb.toString();
0974:
0975: final Throwable err = (Throwable) errs.get(0);
0976: final Desktop desktop = exec.getDesktop();
0977: final Configuration config = desktop.getWebApp()
0978: .getConfiguration();
0979: final String location = config.getErrorPage(desktop
0980: .getDeviceType(), err);
0981: if (location != null) {
0982: try {
0983: exec.setAttribute("javax.servlet.error.message", msg);
0984: exec.setAttribute("javax.servlet.error.exception", err);
0985: exec.setAttribute("javax.servlet.error.exception_type",
0986: err.getClass());
0987: exec.setAttribute("javax.servlet.error.status_code",
0988: new Integer(500));
0989:
0990: //Future: consider to go thru UiFactory for the richlet
0991: //for the error page.
0992: //Challenge: how to call UiFactory.isRichlet
0993: final Richlet richlet = config
0994: .getRichletByPath(location);
0995: if (richlet != null)
0996: richlet.service(getCurrentPage(exec));
0997: else
0998: exec.createComponents(location, null, null);
0999:
1000: //process pending events
1001: //the execution is aborted if an exception is thrown
1002: Event event = nextEvent(uv);
1003: do {
1004: for (; event != null; event = nextEvent(uv)) {
1005: try {
1006: process(desktop, event);
1007: } catch (SuspendNotAllowedException ex) {
1008: //ignore it (possible and reasonable)
1009: }
1010: }
1011: resumeAll(desktop, uv, null);
1012: } while ((event = nextEvent(uv)) != null);
1013: return; //done
1014: } catch (Throwable ex) {
1015: log.realCause("Unable to generate custom error page, "
1016: + location, ex);
1017: }
1018: }
1019:
1020: uv.addResponse(null, new AuAlert(msg)); //default handling
1021: }
1022:
1023: private static final Page getCurrentPage(Execution exec) {
1024: final Page page = ((ExecutionCtrl) exec).getCurrentPage();
1025: return page != null ? page : (Page) exec.getDesktop()
1026: .getPages().iterator().next();
1027: }
1028:
1029: /** Processing the request and stores result into UiVisualizer.
1030: * @param everError whether any error ever occured before processing this
1031: * request.
1032: */
1033: private void process(Execution exec, AuRequest request,
1034: boolean everError) {
1035: // if (log.finable()) log.finer("Processing request: "+request);
1036:
1037: final ExecutionCtrl execCtrl = (ExecutionCtrl) exec;
1038: execCtrl.setCurrentPage(request.getPage());
1039: request.getCommand().process(request, everError);
1040: }
1041:
1042: /** Processing the event and stores result into UiVisualizer. */
1043: private void process(Desktop desktop, Event event) {
1044: // if (log.finable()) log.finer("Processing event: "+event);
1045:
1046: final Component comp = event.getTarget();
1047: if (comp != null) {
1048: processEvent(desktop, comp, event);
1049: } else {
1050: //since an event might change the page/desktop/component relation,
1051: //we copy roots first
1052: final List roots = new LinkedList();
1053: for (Iterator it = desktop.getPages().iterator(); it
1054: .hasNext();) {
1055: roots.addAll(((Page) it.next()).getRoots());
1056: }
1057: for (Iterator it = roots.iterator(); it.hasNext();) {
1058: final Component c = (Component) it.next();
1059: if (c.getPage() != null) //might be removed, so check first
1060: processEvent(desktop, c, event);
1061: }
1062: }
1063: }
1064:
1065: public void wait(Object mutex) throws InterruptedException,
1066: SuspendNotAllowedException {
1067: if (mutex == null)
1068: throw new IllegalArgumentException("null mutex");
1069:
1070: final Thread thd = Thread.currentThread();
1071: if (!(thd instanceof EventProcessingThreadImpl))
1072: throw new UiException(
1073: "This method can be called only in an event listener, not in paging loading.");
1074: // if (log.finerable()) log.finer("Suspend "+thd+" on "+mutex);
1075:
1076: final EventProcessingThreadImpl evtthd = (EventProcessingThreadImpl) thd;
1077: evtthd.newEventThreadSuspends(mutex);
1078: //it may throw an exception, so process it before updating _suspended
1079:
1080: final Execution exec = Executions.getCurrent();
1081: final Desktop desktop = exec.getDesktop();
1082:
1083: incSuspended();
1084:
1085: Map map;
1086: synchronized (_suspended) {
1087: map = (Map) _suspended.get(desktop);
1088: if (map == null)
1089: _suspended.put(desktop, map = new IdentityHashMap(3));
1090: //note: we have to use IdentityHashMap because user might
1091: //use Integer or so as mutex
1092: }
1093: synchronized (map) {
1094: List list = (List) map.get(mutex);
1095: if (list == null)
1096: map.put(mutex, list = new LinkedList());
1097: list.add(evtthd);
1098: }
1099:
1100: try {
1101: EventProcessingThreadImpl.doSuspend(mutex);
1102: } catch (Throwable ex) {
1103: //error recover
1104: synchronized (map) {
1105: final List list = (List) map.get(mutex);
1106: if (list != null) {
1107: list.remove(evtthd);
1108: if (list.isEmpty())
1109: map.remove(mutex);
1110: }
1111: }
1112:
1113: if (ex instanceof InterruptedException)
1114: throw (InterruptedException) ex;
1115: throw UiException.Aide.wrap(ex, "Unable to suspend "
1116: + evtthd);
1117: } finally {
1118: decSuspended();
1119: }
1120: }
1121:
1122: private void incSuspended() {
1123: final int v = _wapp.getConfiguration().getMaxSuspendedThreads();
1124: synchronized (this ) {
1125: if (v >= 0 && _suspCnt >= v)
1126: throw new SuspendNotAllowedException(
1127: MZk.TOO_MANY_SUSPENDED);
1128: ++_suspCnt;
1129: }
1130: }
1131:
1132: private void decSuspended() {
1133: synchronized (this ) {
1134: --_suspCnt;
1135: }
1136: }
1137:
1138: public void notify(Object mutex) {
1139: notify(Executions.getCurrent().getDesktop(), mutex);
1140: }
1141:
1142: public void notify(Desktop desktop, Object mutex) {
1143: if (desktop == null || mutex == null)
1144: throw new IllegalArgumentException(
1145: "desktop and mutex cannot be null");
1146:
1147: final Map map;
1148: synchronized (_suspended) {
1149: map = (Map) _suspended.get(desktop);
1150: }
1151: if (map == null)
1152: return; //nothing to notify
1153:
1154: final EventProcessingThreadImpl evtthd;
1155: synchronized (map) {
1156: final List list = (List) map.get(mutex);
1157: if (list == null)
1158: return; //nothing to notify
1159:
1160: //Note: list is never empty
1161: evtthd = (EventProcessingThreadImpl) list.remove(0);
1162: if (list.isEmpty())
1163: map.remove(mutex); //clean up
1164: }
1165: addResumed(desktop, evtthd);
1166: }
1167:
1168: public void notifyAll(Object mutex) {
1169: final Execution exec = Executions.getCurrent();
1170: if (exec == null)
1171: throw new UiException(
1172: "resume can be called only in processing a request");
1173: notifyAll(exec.getDesktop(), mutex);
1174: }
1175:
1176: public void notifyAll(Desktop desktop, Object mutex) {
1177: if (desktop == null || mutex == null)
1178: throw new IllegalArgumentException(
1179: "desktop and mutex cannot be null");
1180:
1181: final Map map;
1182: synchronized (_suspended) {
1183: map = (Map) _suspended.get(desktop);
1184: }
1185: if (map == null)
1186: return; //nothing to notify
1187:
1188: final List list;
1189: synchronized (map) {
1190: list = (List) map.remove(mutex);
1191: if (list == null)
1192: return; //nothing to notify
1193: }
1194: for (Iterator it = list.iterator(); it.hasNext();)
1195: addResumed(desktop, (EventProcessingThreadImpl) it.next());
1196: }
1197:
1198: /** Adds to _resumed */
1199: private void addResumed(Desktop desktop,
1200: EventProcessingThreadImpl evtthd) {
1201: // if (log.finerable()) log.finer("Ready to resume "+evtthd);
1202:
1203: List list;
1204: synchronized (_resumed) {
1205: list = (List) _resumed.get(desktop);
1206: if (list == null)
1207: _resumed.put(desktop, list = new LinkedList());
1208: }
1209: synchronized (list) {
1210: list.add(evtthd);
1211: }
1212: }
1213:
1214: /** Does the real resume.
1215: * <p>Note 1: the current thread will wait until the resumed threads, if any, complete
1216: * <p>Note 2: {@link #resume} only puts a thread into a resume queue in execution.
1217: */
1218: private void resumeAll(Desktop desktop, UiVisualizer uv, List errs) {
1219: //We have to loop because a resumed thread might resume others
1220: for (;;) {
1221: final List list;
1222: synchronized (_resumed) {
1223: list = (List) _resumed.remove(desktop);
1224: if (list == null)
1225: return; //nothing to resume; done
1226: }
1227:
1228: synchronized (list) {
1229: for (Iterator it = list.iterator(); it.hasNext();) {
1230: final EventProcessingThreadImpl evtthd = (EventProcessingThreadImpl) it
1231: .next();
1232: if (uv.isAborting()) {
1233: evtthd.ceaseSilently("Resume aborted");
1234: } else {
1235: // if (log.finerable()) log.finer("Resume "+evtthd);
1236: try {
1237: if (evtthd.doResume()) //wait it complete or suspend again
1238: recycleEventThread(evtthd); //completed
1239: } catch (Throwable ex) {
1240: recycleEventThread(evtthd);
1241: if (errs == null) {
1242: log.error("Unable to resume " + evtthd,
1243: ex);
1244: throw UiException.Aide.wrap(ex);
1245: }
1246: handleError(ex, uv, errs);
1247: }
1248: }
1249: }
1250: }
1251: }
1252: }
1253:
1254: /** Process an event. */
1255: private void processEvent(Desktop desktop, Component comp,
1256: Event event) {
1257: final Configuration config = desktop.getWebApp()
1258: .getConfiguration();
1259: if (config.isEventThreadEnabled()) {
1260: EventProcessingThreadImpl evtthd = null;
1261: synchronized (_idles) {
1262: if (!_idles.isEmpty())
1263: evtthd = (EventProcessingThreadImpl) _idles
1264: .remove(0);
1265: }
1266:
1267: if (evtthd == null)
1268: evtthd = new EventProcessingThreadImpl();
1269:
1270: try {
1271: if (evtthd.processEvent(desktop, comp, event))
1272: recycleEventThread(evtthd);
1273: } catch (Throwable ex) {
1274: recycleEventThread(evtthd);
1275: throw UiException.Aide.wrap(ex);
1276: }
1277: } else { //event thread disabled
1278: //Note: we don't need to call proc.setup() and cleanup(),
1279: //since they are in the same thread
1280: EventProcessor proc = new EventProcessor(desktop, comp,
1281: event);
1282: //Note: it also checks the correctness
1283: List cleanups = null, errs = null;
1284: try {
1285: final List inits = config.newEventThreadInits(comp,
1286: event);
1287: EventProcessor.inEventListener(true);
1288: if (config.invokeEventThreadInits(inits, comp, event)) //false measn ignore
1289: proc.process();
1290: } catch (Throwable ex) {
1291: errs = new LinkedList();
1292: errs.add(ex);
1293: cleanups = config.newEventThreadCleanups(comp, event,
1294: errs);
1295:
1296: throw UiException.Aide.wrap(ex);
1297: } finally {
1298: EventProcessor.inEventListener(false);
1299: if (errs == null) //not cleanup yet
1300: cleanups = config.newEventThreadCleanups(comp,
1301: event, null);
1302: config.invokeEventThreadCompletes(cleanups, comp,
1303: event, errs);
1304: }
1305: }
1306: }
1307:
1308: private void recycleEventThread(EventProcessingThreadImpl evtthd) {
1309: if (!evtthd.isCeased()) {
1310: if (evtthd.isIdle()) {
1311: final int max = _wapp.getConfiguration()
1312: .getMaxSpareThreads();
1313: synchronized (_idles) {
1314: if (max < 0 || _idles.size() < max) {
1315: _idles.add(evtthd); //return to pool
1316: return; //done
1317: }
1318: }
1319: }
1320: evtthd.ceaseSilently("Recycled");
1321: }
1322: }
1323:
1324: public void activate(Execution exec) {
1325: assert D.OFF || ExecutionsCtrl.getCurrentCtrl() == null : "Impossible to re-activate for update: old="
1326: + ExecutionsCtrl.getCurrentCtrl() + ", new=" + exec;
1327: doActivate(exec, null, false);
1328: }
1329:
1330: public void deactivate(Execution exec) {
1331: doDeactivate(exec);
1332: }
1333:
1334: //-- Common private utilities --//
1335: /** Activates the specified execution for processing.
1336: *
1337: * @param requests a list of requests to process.
1338: * Activation assumes it is asynchronous update if it is not null.
1339: * @param recovering whether it is in recovering, i.e.,
1340: * cause by {@link FailoverManager#recover}.
1341: * If true, the requests argument must be null.
1342: * @return the exec info if the execution is granted;
1343: * null if request has been added to the exec currently activated
1344: */
1345: private static UiVisualizer doActivate(Execution exec,
1346: List requests, boolean recovering) {
1347: if (Executions.getCurrent() != null)
1348: throw new IllegalStateException("Use doReactivate instead");
1349:
1350: final Desktop desktop = exec.getDesktop();
1351: final DesktopCtrl desktopCtrl = (DesktopCtrl) desktop;
1352: final Session sess = desktop.getSession();
1353: // if (log.finerable()) log.finer("Activating "+desktop);
1354:
1355: final boolean asyncupd = requests != null;
1356: assert D.OFF || !recovering || !asyncupd;
1357: //to simplify the following codes, asyncupd and recovering
1358: //cannot be both true
1359:
1360: final boolean inProcess = asyncupd && !isRecovering(desktop)
1361: && desktopCtrl.getRequestQueue().addRequests(requests);
1362: //used as flag to know whether to add the requests
1363: //to the previous execution, if any.
1364:
1365: //lock desktop
1366: final UiVisualizer uv;
1367: final Map eis = getVisualizers(sess);
1368: synchronized (eis) {
1369: for (;;) {
1370: final UiVisualizer old = (UiVisualizer) eis
1371: .get(desktop);
1372: if (old == null)
1373: break; //grantable
1374:
1375: if (inProcess)
1376: return null; //done
1377:
1378: try {
1379: eis.wait(120 * 1000);
1380: } catch (InterruptedException ex) {
1381: throw UiException.Aide.wrap(ex);
1382: }
1383: }
1384:
1385: //grant
1386: if (asyncupd)
1387: desktopCtrl.getRequestQueue().setInProcess();
1388: //set the flag asap to free more following executions
1389: eis.put(desktop, uv = new UiVisualizer(exec, asyncupd,
1390: recovering));
1391: desktopCtrl.setExecution(exec);
1392: }
1393:
1394: final ExecutionCtrl execCtrl = (ExecutionCtrl) exec;
1395: execCtrl.setVisualizer(uv);
1396: ExecutionsCtrl.setCurrent(exec);
1397: execCtrl.onActivate();
1398: return uv;
1399: }
1400:
1401: /** Returns whether the desktop is being recovered.
1402: */
1403: private static final boolean isRecovering(Desktop desktop) {
1404: final Execution exec = desktop.getExecution();
1405: return exec != null && ((ExecutionCtrl) exec).isRecovering();
1406: }
1407:
1408: /** Deactivates the execution. */
1409: private static final void doDeactivate(Execution exec) {
1410: final Desktop desktop = exec.getDesktop();
1411: final Session sess = desktop.getSession();
1412: // if (log.finerable()) log.finer("Deactivating "+desktop);
1413:
1414: final ExecutionCtrl execCtrl = (ExecutionCtrl) exec;
1415: try {
1416: //Unlock desktop
1417: final Map eis = getVisualizers(sess);
1418: synchronized (eis) {
1419: final Object o = eis.remove(desktop);
1420: assert D.OFF || o != null;
1421: ((DesktopCtrl) desktop).setExecution(null);
1422: eis.notify(); //wakeup doActivate's wait
1423: }
1424: } finally {
1425: execCtrl.onDeactivate();
1426: execCtrl.setCurrentPage(null);
1427: execCtrl.setVisualizer(null);
1428: ExecutionsCtrl.setCurrent(null);
1429: }
1430:
1431: final SessionCtrl sessCtrl = (SessionCtrl) sess;
1432: if (sessCtrl.isInvalidated())
1433: sessCtrl.invalidateNow();
1434: }
1435:
1436: /** Re-activates for another execution. It is callable only for
1437: * creating new page (execNewPage). It is not allowed for async-update.
1438: * <p>Note: doActivate cannot handle reactivation. In other words,
1439: * the caller has to detect which method to use.
1440: */
1441: private static UiVisualizer doReactivate(Execution curExec,
1442: UiVisualizer olduv) {
1443: final Desktop desktop = curExec.getDesktop();
1444: final Session sess = desktop.getSession();
1445: // if (log.finerable()) log.finer("Re-activating "+desktop);
1446:
1447: assert D.OFF || olduv.getExecution().getDesktop() == desktop : "old dt: "
1448: + olduv.getExecution().getDesktop()
1449: + ", new:"
1450: + desktop;
1451:
1452: final UiVisualizer uv = new UiVisualizer(olduv, curExec);
1453: final Map eis = getVisualizers(sess);
1454: synchronized (eis) {
1455: final Object o = eis.put(desktop, uv);
1456: if (o != olduv)
1457: throw new InternalError(); //wrong olduv
1458: ((DesktopCtrl) desktop).setExecution(curExec);
1459: }
1460:
1461: final ExecutionCtrl curCtrl = (ExecutionCtrl) curExec;
1462: curCtrl.setVisualizer(uv);
1463: ExecutionsCtrl.setCurrent(curExec);
1464: curCtrl.onActivate();
1465: return uv;
1466: }
1467:
1468: /** De-reactivated exec. Work with {@link #doReactivate}.
1469: */
1470: private static void doDereactivate(Execution curExec,
1471: UiVisualizer olduv) {
1472: if (olduv == null)
1473: throw new IllegalArgumentException("null");
1474:
1475: final Desktop desktop = curExec.getDesktop();
1476: final Session sess = desktop.getSession();
1477: // if (log.finerable()) log.finer("Deactivating "+desktop);
1478:
1479: final ExecutionCtrl curCtrl = (ExecutionCtrl) curExec;
1480: curCtrl.onDeactivate();
1481: curCtrl.setCurrentPage(null);
1482: curCtrl.setVisualizer(null); //free memory
1483:
1484: final Execution oldexec = olduv.getExecution();
1485: final Map eis = getVisualizers(sess);
1486: synchronized (eis) {
1487: eis.put(desktop, olduv);
1488: ((DesktopCtrl) desktop).setExecution(oldexec);
1489: }
1490: ExecutionsCtrl.setCurrent(oldexec);
1491: }
1492:
1493: /** Returns a map of (Page, UiVisualizer). */
1494: private static Map getVisualizers(Session sess) {
1495: synchronized (sess) {
1496: final String attr = "org.zkoss.zk.ui.Visualizers";
1497: Map eis = (Map) sess.getAttribute(attr);
1498: if (eis == null)
1499: sess.setAttribute(attr, eis = new HashMap());
1500: return eis;
1501: }
1502: }
1503:
1504: //Handling Native Component//
1505: /** Sets the prolog of the specified native component.
1506: */
1507: private static final void setProlog(CreateInfo ci, Component comp,
1508: NativeInfo compInfo) {
1509: final Native nc = (Native) comp;
1510: StringBuffer sb = null;
1511: final List prokids = compInfo.getPrologChildren();
1512: if (!prokids.isEmpty()) {
1513: sb = new StringBuffer(256);
1514: getNativeContent(ci, sb, comp, prokids);
1515: }
1516:
1517: final NativeInfo splitInfo = compInfo.getSplitChild();
1518: if (splitInfo != null && splitInfo.isEffective(comp)) {
1519: if (sb == null)
1520: sb = new StringBuffer(256);
1521: getNativeFirstHalf(ci, sb, comp, splitInfo);
1522: }
1523:
1524: if (sb != null && sb.length() > 0)
1525: nc.setPrologContent(sb.insert(0,
1526: (String) nc.getPrologContent()).toString());
1527: }
1528:
1529: /** Sets the epilog of the specified native component.
1530: * @param comp the native component
1531: */
1532: private static final void setEpilog(CreateInfo ci, Component comp,
1533: NativeInfo compInfo) {
1534: final Native nc = (Native) comp;
1535: StringBuffer sb = null;
1536: final NativeInfo splitInfo = compInfo.getSplitChild();
1537: if (splitInfo != null && splitInfo.isEffective(comp)) {
1538: sb = new StringBuffer(256);
1539: getNativeSecondHalf(ci, sb, comp, splitInfo);
1540: }
1541:
1542: final List epikids = compInfo.getEpilogChildren();
1543: if (!epikids.isEmpty()) {
1544: if (sb == null)
1545: sb = new StringBuffer(256);
1546: getNativeContent(ci, sb, comp, epikids);
1547: }
1548:
1549: if (sb != null && sb.length() > 0)
1550: nc.setEpilogContent(sb.append(nc.getEpilogContent())
1551: .toString());
1552: }
1553:
1554: /**
1555: * @param comp the native component
1556: */
1557: private static final void getNativeContent(CreateInfo ci,
1558: StringBuffer sb, Component comp, List children) {
1559: for (Iterator it = children.iterator(); it.hasNext();) {
1560: final Object meta = it.next();
1561: if (meta instanceof NativeInfo) {
1562: final NativeInfo childInfo = (NativeInfo) meta;
1563: final ForEach forEach = childInfo.getForEach(ci.page,
1564: comp);
1565: if (forEach == null) {
1566: if (childInfo.isEffective(comp)) {
1567: getNativeFirstHalf(ci, sb, comp, childInfo);
1568: getNativeSecondHalf(ci, sb, comp, childInfo);
1569: }
1570: } else {
1571: while (forEach.next()) {
1572: if (childInfo.isEffective(comp)) {
1573: getNativeFirstHalf(ci, sb, comp, childInfo);
1574: getNativeSecondHalf(ci, sb, comp, childInfo);
1575: }
1576: }
1577: }
1578: } else if (meta instanceof TextInfo) {
1579: final String s = ((TextInfo) meta).getValue(comp);
1580: ((Native) comp).getHelper().appendText(sb, s);
1581: } else {
1582: execNonComponent(ci, comp, meta);
1583: }
1584: }
1585: }
1586:
1587: /** Before calling this method, childInfo.isEffective must be examined
1588: */
1589: private static final void getNativeFirstHalf(CreateInfo ci,
1590: StringBuffer sb, Component comp, NativeInfo childInfo) {
1591: ((Native) comp).getHelper().getFirstHalf(sb,
1592: childInfo.getTag(),
1593: evalProperties(comp, childInfo.getProperties()),
1594: childInfo.getDeclaredNamespaces());
1595:
1596: final List prokids = childInfo.getPrologChildren();
1597: if (!prokids.isEmpty())
1598: getNativeContent(ci, sb, comp, prokids);
1599:
1600: final NativeInfo splitInfo = childInfo.getSplitChild();
1601: if (splitInfo != null && splitInfo.isEffective(comp))
1602: getNativeFirstHalf(ci, sb, comp, splitInfo); //recursive
1603: }
1604:
1605: /** Before calling this method, childInfo.isEffective must be examined
1606: */
1607: private static final void getNativeSecondHalf(CreateInfo ci,
1608: StringBuffer sb, Component comp, NativeInfo childInfo) {
1609: final NativeInfo splitInfo = childInfo.getSplitChild();
1610: if (splitInfo != null && splitInfo.isEffective(comp))
1611: getNativeSecondHalf(ci, sb, comp, splitInfo); //recursive
1612:
1613: final List epikids = childInfo.getEpilogChildren();
1614: if (!epikids.isEmpty())
1615: getNativeContent(ci, sb, comp, epikids);
1616:
1617: ((Native) comp).getHelper().getSecondHalf(sb,
1618: childInfo.getTag());
1619: }
1620:
1621: /** Returns a map of properties, (String name, String value).
1622: */
1623: private static final Map evalProperties(Component comp, List props) {
1624: if (props == null || props.isEmpty())
1625: return Collections.EMPTY_MAP;
1626:
1627: final Map map = new LinkedHashMap(props.size() * 2);
1628: for (Iterator it = props.iterator(); it.hasNext();) {
1629: final Property prop = (Property) it.next();
1630: if (prop.isEffective(comp))
1631: map.put(prop.getName(), Classes.coerce(String.class,
1632: prop.getValue(comp)));
1633: }
1634: return map;
1635: }
1636:
1637: //Supporting Classes//
1638: /** The listener to create children when the fulfill condition is
1639: * satisfied.
1640: */
1641: private static class FulfillListener implements EventListener,
1642: Express, java.io.Serializable, Cloneable,
1643: ComponentSerializationListener, ComponentCloneListener {
1644: private transient String[] _evtnms;
1645: private transient Component[] _targets;
1646: private transient Component _comp;
1647: private final ComponentInfo _compInfo;
1648: private final String _fulfill;
1649:
1650: private FulfillListener(String fulfill, ComponentInfo compInfo,
1651: Component comp) {
1652: _fulfill = fulfill;
1653: _compInfo = compInfo;
1654: _comp = comp;
1655:
1656: init();
1657:
1658: for (int j = _targets.length; --j >= 0;)
1659: _targets[j].addEventListener(_evtnms[j], this );
1660: }
1661:
1662: private void init() {
1663: final List results = new LinkedList();
1664: for (int j = 0, len = _fulfill.length();;) {
1665: int k = _fulfill.indexOf(',', j);
1666: String sub = (k >= 0 ? _fulfill.substring(j, k)
1667: : _fulfill.substring(j)).trim();
1668: if (sub.length() > 0)
1669: results.add(ComponentsCtrl.parseEventExpression(
1670: _comp, sub, _comp, false));
1671:
1672: if (k < 0 || (j = k + 1) >= len)
1673: break;
1674: }
1675:
1676: int j = results.size();
1677: _targets = new Component[j];
1678: _evtnms = new String[j];
1679: j = 0;
1680: for (Iterator it = results.iterator(); it.hasNext(); ++j) {
1681: final Object[] result = (Object[]) it.next();
1682: _targets[j] = (Component) result[0];
1683: _evtnms[j] = (String) result[1];
1684: }
1685: }
1686:
1687: public void onEvent(Event evt) throws Exception {
1688: for (int j = _targets.length; --j >= 0;)
1689: _targets[j].removeEventListener(_evtnms[j], this ); //one shot only
1690:
1691: final Execution exec = Executions.getCurrent();
1692: execCreate0(
1693: new CreateInfo(((WebAppCtrl) exec.getDesktop()
1694: .getWebApp()).getUiFactory(), exec, _comp
1695: .getPage()), _compInfo, _comp);
1696: }
1697:
1698: //ComponentSerializationListener//
1699: public void willSerialize(Component comp) {
1700: }
1701:
1702: public void didDeserialize(Component comp) {
1703: _comp = comp;
1704: init();
1705: }
1706:
1707: //ComponentCloneListener//
1708: public Object clone(Component comp) {
1709: final FulfillListener clone;
1710: try {
1711: clone = (FulfillListener) clone();
1712: } catch (CloneNotSupportedException e) {
1713: throw new InternalError();
1714: }
1715: clone._comp = comp;
1716: clone.init();
1717: return clone;
1718: }
1719: }
1720:
1721: /** Info used with execCreate
1722: */
1723: private static class CreateInfo {
1724: private final Execution exec;
1725: private final Page page;
1726: private final UiFactory uf;
1727:
1728: private CreateInfo(UiFactory uf, Execution exec, Page page) {
1729: this.exec = exec;
1730: this.page = page;
1731: this.uf = uf;
1732: }
1733: }
1734: }
|