1 /* PageImpl.java
2
3 {{IS_NOTE
4 Purpose:
5
6 Description:
7
8 History:
9 Fri Jun 3 18:17:32 2005, Created by tomyeh
10 }}IS_NOTE
11
12 Copyright (C) 2005 Potix Corporation. All Rights Reserved.
13
14 {{IS_RIGHT
15 This program is distributed under GPL Version 2.0 in the hope that
16 it will be useful, but WITHOUT ANY WARRANTY.
17 }}IS_RIGHT
18 */
19 package org.zkoss.zk.ui.impl;
20
21 import java.util.Iterator;
22 import java.util.ListIterator;
23 import java.util.List;
24 import java.util.LinkedList;
25 import java.util.ArrayList;
26 import java.util.Map;
27 import java.util.HashMap;
28 import java.util.LinkedHashMap;
29 import java.util.AbstractMap;
30 import java.util.Set;
31 import java.util.HashSet;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.io.Writer;
35 import java.io.IOException;
36
37 import org.zkoss.lang.D;
38 import org.zkoss.lang.Classes;
39 import org.zkoss.lang.Objects;
40 import org.zkoss.lang.Strings;
41 import org.zkoss.lang.Exceptions;
42 import org.zkoss.lang.Expectable;
43 import org.zkoss.util.CollectionsX;
44 import org.zkoss.util.logging.Log;
45 import org.zkoss.io.Serializables;
46 import org.zkoss.xel.ExpressionFactory;
47 import org.zkoss.xel.VariableResolver;
48 import org.zkoss.xel.Function;
49 import org.zkoss.xel.FunctionMapper;
50 import org.zkoss.xel.util.DualFunctionMapper;
51
52 import org.zkoss.zk.mesg.MZk;
53 import org.zkoss.zk.ui.WebApp;
54 import org.zkoss.zk.ui.Desktop;
55 import org.zkoss.zk.ui.Richlet;
56 import org.zkoss.zk.ui.Page;
57 import org.zkoss.zk.ui.Session;
58 import org.zkoss.zk.ui.IdSpace;
59 import org.zkoss.zk.ui.Component;
60 import org.zkoss.zk.ui.Executions;
61 import org.zkoss.zk.ui.Execution;
62 import org.zkoss.zk.ui.UiException;
63 import org.zkoss.zk.ui.ComponentNotFoundException;
64 import org.zkoss.zk.ui.event.EventListener;
65 import org.zkoss.zk.ui.event.Events;
66 import org.zkoss.zk.ui.metainfo.PageDefinition;
67 import org.zkoss.zk.ui.metainfo.LanguageDefinition;
68 import org.zkoss.zk.ui.metainfo.ComponentDefinition;
69 import org.zkoss.zk.ui.metainfo.ComponentDefinitionMap;
70 import org.zkoss.zk.ui.metainfo.DefinitionNotFoundException;
71 import org.zkoss.zk.ui.metainfo.ZScript;
72 import org.zkoss.zk.ui.util.Condition;
73 import org.zkoss.zk.ui.util.PageSerializationListener;
74 import org.zkoss.zk.ui.sys.ExecutionCtrl;
75 import org.zkoss.zk.ui.sys.WebAppCtrl;
76 import org.zkoss.zk.ui.sys.DesktopCtrl;
77 import org.zkoss.zk.ui.sys.PageCtrl;
78 import org.zkoss.zk.ui.sys.PageConfig;
79 import org.zkoss.zk.ui.sys.ComponentCtrl;
80 import org.zkoss.zk.ui.sys.ComponentsCtrl;
81 import org.zkoss.zk.ui.sys.Names;
82 import org.zkoss.zk.ui.sys.UiEngine;
83 import org.zkoss.zk.ui.sys.IdGenerator;
84 import org.zkoss.zk.xel.ExValue;
85 import org.zkoss.zk.au.out.AuSetTitle;
86 import org.zkoss.zk.scripting.Interpreter;
87 import org.zkoss.zk.scripting.Interpreters;
88 import org.zkoss.zk.scripting.HierachicalAware;
89 import org.zkoss.zk.scripting.SerializableAware;
90 import org.zkoss.zk.scripting.Namespace;
91 import org.zkoss.zk.scripting.InterpreterNotFoundException;
92 import org.zkoss.zk.scripting.util.AbstractNamespace;
93
94 /**
95 * An implmentation of {@link Page} and {@link PageCtrl}.
96 * Refer to them for more details.
97 *
98 * <p>Note: though {@link PageImpl} is serializable, it is designed
99 * to work with Web container to enable the serialization of sessions.
100 * It is not suggested to serialize and desrialize it directly since
101 * many fields might be lost.
102 *
103 * <p>On the other hand, it is OK to serialize and deserialize
104 * {@link Component}.
105 *
106 * <p>Implementation Notes:<br>
107 * It is not thread-safe because it is protected by the spec:
108 * at most one thread can access a page and all its components at the same time.
109 *
110 * @author tomyeh
111 */
112 public class PageImpl implements Page, PageCtrl, java.io.Serializable {
113 private static final Log log = Log.lookup(PageImpl.class);
114 private static final Log _zklog = Log.lookup("org.zkoss.zk.log");
115 private static final long serialVersionUID = 20070413L;
116
117 /** URI for redrawing as a desktop or part of another desktop. */
118 private final ExValue _cplURI, _dkURI, _pgURI;
119 /** The component that includes this page, or null if not included. */
120 private transient Component _owner;
121 /** Used to retore _owner. */
122 private transient String _ownerUuid;
123 private transient Desktop _desktop;
124 private String _id, _uuid;
125 private String _title = "", _style = "";
126 private final String _path;
127 private String _zslang;
128 /** A list of deferred zscript [Component parent, {@link ZScript}]. */
129 private List _zsDeferred;
130 /** A list of root components. */
131 private final List _roots = new LinkedList();
132 private transient List _roRoots;
133 /** A map of fellows. */
134 private transient Map _fellows;
135 /** A map of attributes. */
136 private transient Map _attrs;
137 /** A map of event listener: Map(evtnm, List(EventListener)). */
138 private transient Map _listeners;
139 /** The default parent. */
140 private transient Component _defparent;
141 /** The reason to store it is PageDefinition is not serializable. */
142 private FunctionMapper _mapper;
143 /** The reason to store it is PageDefinition is not serializable. */
144 private ComponentDefinitionMap _compdefs;
145 /** The reason to store it is PageDefinition is not serializable. */
146 private transient LanguageDefinition _langdef;
147 /** The header tags. */
148 private String _headers = "";
149 /** The root attributes. */
150 private String _rootAttrs = "";
151 private String _contentType, _docType, _firstLine;
152 private Boolean _cacheable;
153 /** The expression factory (ExpressionFactory).*/
154 private Class _expfcls;
155 /** A map of interpreters Map(String zslang, Interpreter ip). */
156 private transient Map _ips;
157 private transient NS _ns;
158 /** A list of {@link VariableResolver}. */
159 private transient List _resolvers;
160 private boolean _complete;
161
162 /** Constructs a page by giving the page definition.
163 *
164 * <p>Note: when a page is constructed, it doesn't belong to a desktop
165 * yet. Caller has to invoke {@link #init} to complete
166 * the creation of a page.
167 * Why two phase? Contructor could be called before execution
168 * is activated, but {@link #init} must be called in an execution.
169 *
170 * <p>Also note that {@link #getId} and {@link #getTitle}
171 * are not ready until {@link #init} is called.
172 *
173 * @param pgdef the page definition (never null).
174 */
175 public PageImpl(PageDefinition pgdef) {
176 this(pgdef.getLanguageDefinition(), pgdef.getComponentDefinitionMap(),
177 pgdef.getRequestPath(), pgdef.getZScriptLanguage());
178 }
179 /** Constructs a page without page definition and richlet.
180 *
181 * @param langdef the language definition (never null)
182 * @param compdefs the component definition map.
183 * If null, an empty map is assumed.
184 * @param path the request path. If null, empty is assumed.
185 * @param zslang the zscript language. If null, "Java" is assumed.
186 */
187 public PageImpl(LanguageDefinition langdef,
188 ComponentDefinitionMap compdefs, String path, String zslang) {
189 init();
190
191 _langdef = langdef;
192 _cplURI = new ExValue(_langdef.getCompleteURI(), String.class);
193 _dkURI = new ExValue(_langdef.getDesktopURI(), String.class);
194 _pgURI = new ExValue(_langdef.getPageURI(), String.class);
195 _compdefs = compdefs != null ? compdefs:
196 new ComponentDefinitionMap(
197 _langdef.getComponentDefinitionMap().isCaseInsensitive());
198 _path = path != null ? path: "";
199 _zslang = zslang != null ? zslang: "Java";
200 }
201
202 /** Constructs a page by specifying a richlet.
203 *
204 * <p>Note: when a page is constructed, it doesn't belong to a desktop
205 * yet. Caller has to invoke {@link #init} to complete
206 * the creation of a page.
207 *
208 * <p>Also note that {@link #getId} and {@link #getTitle}
209 * are not ready until {@link #init} is called.
210 *
211 * @param richlet the richlet to serve this page.
212 * @param path the request path, or null if not available
213 */
214 public PageImpl(Richlet richlet, String path) {
215 init();
216
217 _langdef = richlet.getLanguageDefinition();
218 _cplURI = new ExValue(_langdef.getCompleteURI(), String.class);
219 _dkURI = new ExValue(_langdef.getDesktopURI(), String.class);
220 _pgURI = new ExValue(_langdef.getPageURI(), String.class);
221 _compdefs = new ComponentDefinitionMap(
222 _langdef.getComponentDefinitionMap().isCaseInsensitive());
223 _path = path != null ? path: "";
224 _zslang = "Java";
225 }
226 /** Initialized the page when contructed or deserialized.
227 */
228 protected void init() {
229 _ips = new LinkedHashMap(3);
230 _ns = new NS();
231 _roRoots = Collections.unmodifiableList(_roots);
232 _attrs = new HashMap();
233 _fellows = new HashMap();
234 }
235
236 /** Returns the UI engine.
237 */
238 private final UiEngine getUiEngine() {
239 return ((WebAppCtrl)_desktop.getWebApp()).getUiEngine();
240 }
241
242 //-- Page --//
243 private final Execution getExecution() {
244 return _desktop != null ? _desktop.getExecution():
245 Executions.getCurrent();
246 }
247 public final FunctionMapper getFunctionMapper() {
248 return _mapper;
249 }
250 public void addFunctionMapper(FunctionMapper mapper) {
251 _mapper = DualFunctionMapper.combine(mapper, _mapper);
252 }
253
254 public String getRequestPath() {
255 return _path;
256 }
257 public final String getId() {
258 return _id;
259 }
260 public final String getUuid() {
261 return _uuid;
262 }
263 public void setId(String id) {
264 if (_desktop != null)
265 throw new UiException("Unable to change the identifier after the page is initialized");
266 if (id != null && id.length() > 0) _id = id;
267 //No need to update client since it is allowed only before init(...)
268 }
269 public String getTitle() {
270 return _title;
271 }
272 public void setTitle(String title) {
273 if (title == null) title = "";
274 if (!_title.equals(title)) {
275 _title = title;
276 if (_desktop != null) {
277 final Execution exec = getExecution();
278 if (_title.length() > 0) {
279 _title = (String)exec.evaluate(this, _title, String.class);
280 if (_title == null) _title = "";
281 }
282
283 if (exec.isAsyncUpdate(this))
284 getUiEngine().addResponse("setTitle", new AuSetTitle(_title));
285 }
286 }
287 }
288 public String getStyle() {
289 return _style;
290 }
291 public void setStyle(String style) {
292 if (style == null) style = "";
293 if (!_style.equals(style)) {
294 _style = style;
295 if (_desktop != null) {
296 final Execution exec = getExecution();
297 if (_style.length() > 0) {
298 _style = (String)exec.evaluate(this, _style, String.class);
299 if (_style == null) _style = "";
300 }
301 //FUTURE: might support the change of style dynamically
302 }
303 }
304 }
305
306 public Collection getRoots() {
307 return _roRoots;
308 }
309
310 public Map getAttributes(int scope) {
311 switch (scope) {
312 case DESKTOP_SCOPE:
313 return _desktop != null ?
314 _desktop.getAttributes(): Collections.EMPTY_MAP;
315 case SESSION_SCOPE:
316 return _desktop != null ?
317 _desktop.getSession().getAttributes(): Collections.EMPTY_MAP;
318 case APPLICATION_SCOPE:
319 return _desktop != null ?
320 _desktop.getWebApp().getAttributes(): Collections.EMPTY_MAP;
321 case PAGE_SCOPE:
322 return _attrs;
323 case REQUEST_SCOPE:
324 final Execution exec = getExecution();
325 if (exec != null) return exec.getAttributes();
326 //fall thru
327 default:
328 return Collections.EMPTY_MAP;
329 }
330 }
331 public Object getAttribute(String name, int scope) {
332 return getAttributes(scope).get(name);
333 }
334 public Object setAttribute(String name, Object value, int scope) {
335 if (value != null) {
336 final Map attrs = getAttributes(scope);
337 if (attrs == Collections.EMPTY_MAP)
338 throw new IllegalStateException("This component doesn't belong to any ID space: "+this);
339 return attrs.put(name, value);
340 } else {
341 return removeAttribute(name, scope);
342 }
343 }
344 public Object removeAttribute(String name, int scope) {
345 final Map attrs = getAttributes(scope);
346 if (attrs == Collections.EMPTY_MAP)
347 throw new IllegalStateException("This component doesn't belong to any ID space: "+this);
348 return attrs.remove(name);
349 }
350
351 public Map getAttributes() {
352 return _attrs;
353 }
354 public Object getAttribute(String name) {
355 return _attrs.get(name);
356 }
357 public Object setAttribute(String name, Object value) {
358 return value != null ? _attrs.put(name, value): removeAttribute(name);
359 }
360 public Object removeAttribute(String name) {
361 return _attrs.remove(name);
362 }
363
364 public void invalidate() {
365 getUiEngine().addInvalidate(this);
366 }
367 public void removeComponents() {
368 for (Iterator it = new ArrayList(getRoots()).iterator();
369 it.hasNext();)
370 ((Component)it.next()).detach();
371 }
372
373 public Class resolveClass(String clsnm) throws ClassNotFoundException {
374 try {
375 return Classes.forNameByThread(clsnm);
376 } catch (ClassNotFoundException ex) {
377 for (Iterator it = getLoadedInterpreters().iterator();
378 it.hasNext();) {
379 final Class c = ((Interpreter)it.next()).getClass(clsnm);
380 if (c != null)
381 return c;
382 }
383 throw ex;
384 }
385 }
386 public void setVariable(String name, Object val) {
387 _ns.setVariable(name, val, true);
388 }
389 public boolean containsVariable(String name) {
390 return _ns.containsVariable(name, true);
391 }
392 public Object getVariable(String name) {
393 return _ns.getVariable(name, true);
394 }
395 public void unsetVariable(String name) {
396 _ns.unsetVariable(name, true);
397 }
398 public Class getZScriptClass(String clsnm) {
399 for (Iterator it = getLoadedInterpreters().iterator();
400 it.hasNext();) {
401 Class cls = ((Interpreter)it.next()).getClass(clsnm);
402 if (cls != null)
403 return cls;
404 }
405
406 try {
407 return Classes.forNameByThread(clsnm);
408 } catch (ClassNotFoundException ex) {
409 return null;
410 }
411 }
412 public Function getZScriptFunction(String name, Class[] argTypes) {
413 for (Iterator it = getLoadedInterpreters().iterator();
414 it.hasNext();) {
415 Function mtd =
416 ((Interpreter)it.next()).getFunction(name, argTypes);
417 if (mtd != null)
418 return mtd;
419 }
420 return null;
421 }
422 public Function getZScriptFunction(Namespace ns, String name, Class[] argTypes) {
423 for (Iterator it = getLoadedInterpreters().iterator();
424 it.hasNext();) {
425 final Object ip = it.next();
426 Function mtd =
427 ip instanceof HierachicalAware ?
428 ((HierachicalAware)ip).getFunction(ns, name, argTypes):
429 ((Interpreter)ip).getFunction(name, argTypes);
430 if (mtd != null)
431 return mtd;
432 }
433 return null;
434 }
435 public Function getZScriptFunction(
436 Component comp, String name, Class[] argTypes) {
437 return getZScriptFunction(comp != null ? comp.getNamespace(): null,
438 name, argTypes);
439 }
440 /** @deprecated As of release 3.0.0, replaced by {@link #getZScriptFunction(String,Class[])}.
441 */
442 public org.zkoss.zk.scripting.Method
443 getZScriptMethod(String name, Class[] argTypes) {
444 final Function fun = getZScriptFunction(name, argTypes);
445 return fun != null ? new FuncMethod(fun): null;
446 }
447 /** @deprecated As of release 3.0.0, replaced by {@link #getZScriptFunction(String,Class[])}.
448 */
449 public org.zkoss.zk.scripting.Method
450 getZScriptMethod(Namespace ns, String name, Class[] argTypes) {
451 final Function fun = getZScriptFunction(ns, name, argTypes);
452 return fun != null ? new FuncMethod(fun): null;
453 }
454 public Object getZScriptVariable(String name) {
455 for (Iterator it = getLoadedInterpreters().iterator();
456 it.hasNext();) {
457 final Object val = ((Interpreter)it.next()).getVariable(name);
458 if (val != null)
459 return val;
460 }
461 return null;
462 }
463 public Object getZScriptVariable(Namespace ns, String name) {
464 for (Iterator it = getLoadedInterpreters().iterator();
465 it.hasNext();) {
466 final Object ip = it.next();
467 final Object val = ip instanceof HierachicalAware ?
468 ((HierachicalAware)ip).getVariable(ns, name):
469 ((Interpreter)ip).getVariable(name);
470 if (val != null)
471 return val;
472 }
473 return null;
474 }
475 public Object getZScriptVariable(Component comp, String name) {
476 return getZScriptVariable(comp != null ? comp.getNamespace(): null,
477 name);
478 }
479
480 public Object getXelVariable(String name) {
481 final VariableResolver resolv =
482 getExecution().getVariableResolver();
483 return resolv != null ? resolv.resolveVariable(name): null;
484 }
485 /** @deprecated As of release of 3.0.0, replaced with {@link #getXelVariable}.
486 */
487 public Object getELVariable(String name) {
488 return getXelVariable(name);
489 }
490
491 /** Resolves the variable defined in variable resolvers.
492 */
493 private Object resolveVariable(String name) {
494 if (_resolvers != null) {
495 for (Iterator it = _resolvers.iterator(); it.hasNext();) {
496 Object o = ((VariableResolver)it.next()).resolveVariable(name);
497 if (o != null)
498 return o;
499 }
500 }
501 return null;
502 }
503 public boolean addVariableResolver(VariableResolver resolver) {
504 if (resolver == null)
505 throw new IllegalArgumentException("null");
506
507 if (_resolvers == null)
508 _resolvers = new LinkedList();
509 else if (_resolvers.contains(resolver))
510 return false;
511
512 _resolvers.add(0, resolver); //FILO order
513 return true;
514 }
515 public boolean removeVariableResolver(VariableResolver resolver) {
516 return _resolvers != null && _resolvers.remove(resolver);
517 }
518
519 public boolean addEventListener(String evtnm, EventListener listener) {
520 if (evtnm == null || listener == null)
521 throw new IllegalArgumentException("null");
522 if (!Events.isValid(evtnm))
523 throw new IllegalArgumentException("Invalid event name: "+evtnm);
524
525 if (_listeners == null)
526 _listeners = new HashMap(3);
527
528 List l = (List)_listeners.get(evtnm);
529 if (l != null) {
530 for (Iterator it = l.iterator(); it.hasNext();) {
531 final EventListener li = (EventListener)it.next();
532 if (listener.equals(li))
533 return false;
534 }
535 } else {
536 _listeners.put(evtnm, l = new LinkedList());
537 }
538 l.add(listener);
539 return true;
540 }
541 public boolean removeEventListener(String evtnm, EventListener listener) {
542 if (evtnm == null || listener == null)
543 throw new NullPointerException();
544
545 if (_listeners != null) {
546 final List l = (List)_listeners.get(evtnm);
547 if (l != null) {
548 for (Iterator it = l.iterator(); it.hasNext();) {
549 final EventListener li = (EventListener)it.next();
550 if (listener.equals(li)) {
551 if (l.size() == 1)
552 _listeners.remove(evtnm);
553 else
554 it.remove();
555 return true;
556 }
557 }
558 }
559 }
560 return false;
561 }
562
563 public Component getFellow(String compId) {
564 final Component comp = (Component)_fellows.get(compId);
565 if (comp == null)
566 if (compId != null && ComponentsCtrl.isAutoId(compId))
567 throw new ComponentNotFoundException(MZk.AUTO_ID_NOT_LOCATABLE, compId);
568 else
569 throw new ComponentNotFoundException("Fellow component not found: "+compId);
570 return comp;
571 }
572 public Component getFellowIfAny(String compId) {
573 return (Component)_fellows.get(compId);
574 }
575 public Collection getFellows() {
576 return Collections.unmodifiableCollection(_fellows.values());
577 }
578
579 public boolean isComplete() {
580 return _complete;
581 }
582 public void setComplete(boolean complete) {
583 _complete = complete;
584 }
585
586 //-- PageCtrl --//
587 public void init(PageConfig config) {
588 if (_desktop != null)
589 throw new IllegalStateException("Don't init twice");
590
591 final Execution exec = Executions.getCurrent();
592 _desktop = exec.getDesktop();
593 if (_desktop == null)
594 throw new IllegalArgumentException("null desktop");
595
596 initVariables();
597
598 if (((ExecutionCtrl)exec).isRecovering()) {
599 final String uuid = config.getUuid(), id = config.getId();
600 if (uuid == null || id == null)
601 throw new IllegalArgumentException("both id and uuid are required in recovering");
602 _uuid = uuid;
603 _id = id;
604 } else {
605 final IdGenerator idgen =
606 ((WebAppCtrl)_desktop.getWebApp()).getIdGenerator();
607 if (idgen != null)
608 _uuid = idgen.nextPageUuid(this);
609 if (_uuid == null)
610 _uuid = ((DesktopCtrl)_desktop).getNextUuid();
611
612 if (_id == null) {
613 final String id = config.getId();
614 if (id != null && id.length() != 0) _id = id;
615 }
616 if (_id != null)
617 _id = (String)exec.evaluate(this, _id, String.class);
618 if (_id != null && _id.length() != 0) {
619 final String INVALID = ".&\\%";
620 if (Strings.anyOf(_id, INVALID, 0) < _id.length())
621 throw new IllegalArgumentException("Invalid page ID: "+_id+". Invalid characters: "+INVALID);
622 } else {
623 _id = _uuid;
624 }
625 }
626
627 String s = config.getHeaders();
628 if (s != null) _headers = s;
629
630 if (_title.length() == 0) {
631 s = config.getTitle();
632 if (s != null) setTitle(s);
633 }
634 if (_style.length() == 0) {
635 s = config.getStyle();
636 if (s != null) setStyle(s);
637 }
638
639 ((DesktopCtrl)_desktop).addPage(this);
640 }
641 private void initVariables() {
642 setVariable("log", _zklog);
643 setVariable("page", this);
644 setVariable("pageScope", getAttributes());
645 setVariable("requestScope", REQUEST_ATTRS);
646 setVariable("spaceOwner", this);
647
648 if (_desktop != null) {
649 setVariable("desktop", _desktop);
650 setVariable("desktopScope", _desktop.getAttributes());
651 final WebApp wapp = _desktop.getWebApp();
652 setVariable("application", wapp);
653 setVariable("applicationScope", wapp.getAttributes());
654 final Session sess = _desktop.getSession();
655 setVariable("session", sess);
656 setVariable("sessionScope", sess.getAttributes());
657 }
658 }
659 public void destroy() {
660 for (Iterator it = getLoadedInterpreters().iterator(); it.hasNext();) {
661 final Interpreter ip = (Interpreter)it.next();
662 try {
663 ip.destroy();
664 } catch (Throwable ex) {
665 log.error("Failed to destroy "+ip, ex);
666 }
667 }
668 _ips.clear();
669
670 //theorectically, the following is not necessary, but, to be safe...
671 _roots.clear();
672 _attrs.clear();
673 _fellows = new HashMap(1); //not clear() since # of fellows might huge
674 _ips = null; //not clear since it is better to NPE than memory leak
675 _desktop = null;
676 _owner = _defparent = null;
677 _listeners = null;
678 _ns = null;
679 _resolvers = null;
680 }
681
682 private static final Map REQUEST_ATTRS = new AbstractMap() {
683 public Set entrySet() {
684 final Execution exec = Executions.getCurrent();
685 if (exec == null) return Collections.EMPTY_SET;
686 return exec.getAttributes().entrySet();
687 }
688 public Object put(Object name, Object value) {
689 final Execution exec = Executions.getCurrent();
690 if (exec == null) throw new IllegalStateException("No execution at all");
691 return exec.getAttributes().put(name, value);
692 }
693 public boolean containsKey(Object name) {
694 final Execution exec = Executions.getCurrent();
695 return exec != null && exec.getAttributes().containsKey(name);
696 }
697 public Object get(Object name) {
698 final Execution exec = Executions.getCurrent();
699 if (exec == null) return null;
700 return exec.getAttributes().get(name);
701 }
702 public Object remove(Object name) {
703 final Execution exec = Executions.getCurrent();
704 if (exec == null) return null;
705 return exec.getAttributes().remove(name);
706 }
707 };
708
709 public String getHeaders() {
710 return _headers;
711 }
712 public String getRootAttributes() {
713 return _rootAttrs;
714 }
715 public void setRootAttributes(String rootAttrs) {
716 _rootAttrs = rootAttrs != null ? rootAttrs: "";
717 }
718 public String getContentType() {
719 return _contentType;
720 }
721 public void setContentType(String contentType) {
722 _contentType = contentType;
723 }
724 public String getDocType() {
725 return _docType;
726 }
727 public void setDocType(String docType) {
728 _docType = docType;
729 }
730 public String getFirstLine() {
731 return _firstLine;
732 }
733 public void setFirstLine(String firstLine) {
734 _firstLine = firstLine;
735 }
736 public Boolean getCacheable() {
737 return _cacheable;
738 }
739 public void setCacheable(Boolean cacheable) {
740 _cacheable = cacheable;
741 }
742
743 public final Desktop getDesktop() {
744 return _desktop;
745 }
746 public void addRoot(Component comp) {
747 assert D.OFF || comp.getParent() == null;
748 for (Iterator it = _roots.iterator(); it.hasNext();)
749 if (comp == it.next())
750 return;
751 _roots.add(comp);
752 }
753 public void removeRoot(Component comp) {
754 for (Iterator it = _roots.iterator(); it.hasNext();)
755 if (comp == it.next()) {
756 it.remove();
757 return;
758 }
759 }
760 public void moveRoot(Component comp, Component refRoot) {
761 if (comp.getPage() != this || comp.getParent() != null)
762 return; //nothing to do
763
764 boolean added = false, found = false;
765 for (ListIterator it = _roots.listIterator(); it.hasNext();) {
766 final Object o = it.next();
767 if (o == comp) {
768 if (!added) {
769 if (!it.hasNext()) return; //last
770 if (it.next() == refRoot) return; //same position
771 it.previous(); it.previous(); it.next(); //restore cursor
772 }
773 it.remove();
774 found = true;
775 if (added || refRoot == null) break; //done
776 } else if (o == refRoot) {
777 it.previous();
778 it.add(comp);
779 it.next();
780 added = true;
781 if (found) break; //done
782 }
783 }
784
785 if (!added) _roots.add(comp);
786 }
787
788 public void addFellow(Component comp) {
789 final String compId = comp.getId();
790 assert D.OFF || !ComponentsCtrl.isAutoId(compId);
791
792 final Object old = _fellows.put(compId, comp);
793 if (old != comp) { //possible due to recursive call
794 if (old != null) {
795 _fellows.put(((Component)old).getId(), old); //recover
796 throw new InternalError("Called shall prevent replicated ID for roots");
797 }
798 }
799 }
800 public void removeFellow(Component comp) {
801 _fellows.remove(comp.getId());
802 }
803 public boolean hasFellow(String compId) {
804 return _fellows.containsKey(compId);
805 }
806
807 public void redraw(Collection responses, Writer out) throws IOException {
808 if (log.debugable()) log.debug("Redrawing page: "+this+", roots="+_roots);
809
810 final Execution exec = getExecution();
811 final ExecutionCtrl execCtrl = (ExecutionCtrl)exec;
812 final boolean asyncUpdate = execCtrl.getVisualizer().isEverAsyncUpdate();
813 final boolean bIncluded = asyncUpdate || exec.isIncluded()
814 || exec.getAttribute(ATTR_REDRAW_BY_INCLUDE) != null;
815 final String uri = (String)
816 (_complete ? _cplURI: bIncluded ? _pgURI: _dkURI)
817 .getValue(_langdef.getEvaluator(), this);
818 //desktop and page URI is defined in language
819
820 final Map attrs = new HashMap(6);
821 attrs.put("page", this);
822 attrs.put("asyncUpdate", Boolean.valueOf(asyncUpdate));
823 attrs.put("action", "/"); //A non-empty string (backward compatible)
824 attrs.put("responses",
825 responses != null ? responses: Collections.EMPTY_LIST);
826 if (bIncluded) {
827 attrs.put("included", "true");
828 //maintain original state since desktop will include page...
829 exec.include(out, uri, attrs, Execution.PASS_THRU_ATTR);
830 } else {
831 //FUTURE: Consider if config.isKeepDesktopAcrossVisits() implies cacheable
832 //Why yes: the client doesn't need to ask the server for updated content
833 //Why no: browsers seems fail to handle DHTML correctly (when BACK to
834 //a DHTML page), so it is better to let the server handle cache, if any
835 final boolean cacheable =
836 _cacheable != null ? _cacheable.booleanValue():
837 _desktop.getDevice().isCacheable();
838 if (!cacheable) {
839 //Bug 1520444
840 execCtrl.setHeader("Pragma", "no-cache");
841 execCtrl.addHeader("Cache-Control", "no-cache");
842 execCtrl.addHeader("Cache-Control", "no-store");
843 //execCtrl.addHeader("Cache-Control", "private");
844 //execCtrl.addHeader("Cache-Control", "max-age=0");
845 //execCtrl.addHeader("Cache-Control", "s-maxage=0");
846 //execCtrl.addHeader("Cache-Control", "must-revalidate");
847 //execCtrl.addHeader("Cache-Control", "proxy-revalidate");
848 //execCtrl.addHeader("Cache-Control", "post-check=0");
849 //execCtrl.addHeader("Cache-Control", "pre-check=0");
850 execCtrl.setHeader("Expires", "-1");
851
852 exec.setAttribute(Attributes.NO_CACHE, Boolean.TRUE);
853 //so ZkFns.outLangJavaScripts generates zk.keepDesktop correctly
854 }
855 exec.forward(out, uri, attrs, Execution.PASS_THRU_ATTR);
856 //Don't use include. Otherwise, headers will be gone.
857 }
858 }
859
860 public final Namespace getNamespace() {
861 return _ns;
862 }
863 public void interpret(String zslang, String script, Namespace ns) {
864 getInterpreter(zslang).interpret(script, ns);
865 }
866 public Interpreter getInterpreter(String zslang) {
867 zslang = (zslang != null ? zslang: _zslang).toLowerCase();
868 Interpreter ip = (Interpreter)_ips.get(zslang);
869 if (ip == null) {
870 ip = Interpreters.newInterpreter(zslang, this);
871 _ips.put(zslang, ip);
872 //set first to avoid dead loop if script calls interpret again
873
874 final String script = _langdef.getInitScript(zslang);
875 if (script != null) {
876 _ns.setVariable("log", _zklog, true);
877 _ns.setVariable("page", this, true);
878 ip.interpret(script, _ns);
879 }
880
881 //evaluate deferred zscripts, if any
882 try {
883 evalDeferredZScripts(ip, zslang);
884 } catch (IOException ex) {
885 throw new UiException(ex);
886 }
887 }
888 return ip;
889 }
890
891 public Collection getLoadedInterpreters() {
892 return _ips != null ? _ips.values(): Collections.EMPTY_LIST; //just in case
893 }
894 public String getZScriptLanguage() {
895 return _zslang;
896 }
897 public void setZScriptLanguage(String zslang)
898 throws InterpreterNotFoundException {
899 if (!Objects.equals(zslang, _zslang)) {
900 if (!Interpreters.exists(zslang))
901 throw new InterpreterNotFoundException(zslang, MZk.NOT_FOUND, zslang);
902 _zslang = zslang;
903 }
904 }
905 public void addDeferredZScript(Component parent, ZScript zscript) {
906 if (zscript != null) {
907 if (_zsDeferred == null)
908 _zsDeferred = new LinkedList();
909 _zsDeferred.add(new Object[] {parent, zscript});
910 }
911 }
912 /** Evaluates the deferred zscript.
913 * It is called when the interpreter is loaded
914 */
915 private void evalDeferredZScripts(Interpreter ip, String zslang)
916 throws IOException {
917 if (_zsDeferred != null) {
918 for (Iterator it = _zsDeferred.iterator(); it.hasNext();) {
919 final Object[] zsInfo = (Object[])it.next();
920 final ZScript zscript = (ZScript)zsInfo[1];
921 String targetlang = zscript.getLanguage();
922 if (targetlang == null)
923 targetlang = _zslang; //use default
924
925 if (targetlang.equalsIgnoreCase(zslang)) { //case insensitive
926 it.remove(); //done
927
928 final Component parent = (Component)zsInfo[0];
929 if ((parent == null || parent.getPage() == this)
930 && isEffective(zscript, parent)) {
931 ip.interpret(zscript.getContent(this, parent),
932 parent != null ? parent.getNamespace(): _ns);
933 }
934 }
935 }
936
937 if (_zsDeferred.isEmpty())
938 _zsDeferred = null;
939 }
940 }
941 private boolean isEffective(Condition cond, Component comp) {
942 return comp != null ? cond.isEffective(comp): cond.isEffective(this);
943 }
944
945 public boolean isListenerAvailable(String evtnm) {
946 if (_listeners != null) {
947 final List l = (List)_listeners.get(evtnm);
948 return l != null && !l.isEmpty();
949 }
950 return false;
951 }
952 public Iterator getListenerIterator(String evtnm) {
953 if (_listeners != null) {
954 final List l = (List)_listeners.get(evtnm);
955 if (l != null)
956 return new ListenerIterator(l);
957 }
958 return CollectionsX.EMPTY_ITERATOR;
959 }
960
961 public final Component getOwner() {
962 return _owner;
963 }
964 public final void setOwner(Component comp) {
965 if (_owner != null)
966 throw new IllegalStateException("owner can be set only once");
967 _owner = comp;
968 }
969
970 public Component getDefaultParent() {
971 return _defparent;
972 }
973 public void setDefaultParent(Component comp) {
974 _defparent = comp;
975 }
976
977 public void sessionWillPassivate(Desktop desktop) {
978 for (Iterator it = _roots.iterator(); it.hasNext();)
979 ((ComponentCtrl)it.next()).sessionWillPassivate(this);
980 }
981 public void sessionDidActivate(Desktop desktop) {
982 _desktop = desktop;
983
984 if (_ownerUuid != null) {
985 _owner = _desktop.getComponentByUuid(_ownerUuid);
986 _ownerUuid = null;
987 }
988
989 for (Iterator it = _roots.iterator(); it.hasNext();)
990 ((ComponentCtrl)it.next()).sessionDidActivate(this);
991
992 initVariables(); //since some variables depend on desktop
993 }
994
995 public LanguageDefinition getLanguageDefinition() {
996 return _langdef;
997 }
998 public ComponentDefinitionMap getComponentDefinitionMap() {
999 return _compdefs;
1000 }
1001 public ComponentDefinition getComponentDefinition(String name, boolean recur) {
1002 final ComponentDefinition compdef = _compdefs.get(name);
1003 if (!recur || compdef != null)
1004 return compdef;
1005
1006 try {
1007 return _langdef.getComponentDefinition(name);
1008 } catch (DefinitionNotFoundException ex) {
1009 }
1010 return null;
1011 }
1012 public ComponentDefinition getComponentDefinition(Class cls, boolean recur) {
1013 final ComponentDefinition compdef = _compdefs.get(cls);
1014 if (!recur || compdef != null)
1015 return compdef;
1016
1017 try {
1018 return _langdef.getComponentDefinition(cls);
1019 } catch (DefinitionNotFoundException ex) {
1020 }
1021 return null;
1022 }
1023 public Class getExpressionFactoryClass() {
1024 return _expfcls;
1025 }
1026 public void setExpressionFactoryClass(Class expfcls) {
1027 if (expfcls != null && !ExpressionFactory.class.isAssignableFrom(expfcls))
1028 throw new IllegalArgumentException(expfcls+" must implement "+ExpressionFactory.class);
1029 _expfcls = expfcls;
1030 }
1031
1032 //-- Serializable --//
1033 //NOTE: they must be declared as private
1034 private synchronized void writeObject(java.io.ObjectOutputStream s)
1035 throws java.io.IOException {
1036 s.defaultWriteObject();
1037
1038 s.writeObject(_langdef != null ? _langdef.getName(): null);
1039 s.writeObject(_owner != null ? _owner.getUuid(): null);
1040 s.writeObject(_defparent != null ? _defparent.getUuid(): null);
1041
1042 willSerialize(_attrs.values());
1043 Serializables.smartWrite(s, _attrs);
1044
1045 if (_listeners != null)
1046 for (Iterator it = _listeners.entrySet().iterator(); it.hasNext();) {
1047 final Map.Entry me = (Map.Entry)it.next();
1048 s.writeObject(me.getKey());
1049
1050 final Collection ls = (Collection)me.getValue();
1051 willSerialize(ls);
1052 Serializables.smartWrite(s, ls);
1053 }
1054 s.writeObject(null);
1055
1056 willSerialize(_resolvers);
1057 Serializables.smartWrite(s, _resolvers);
1058
1059 //handle namespace
1060 for (Iterator it = _ns._vars.entrySet().iterator(); it.hasNext();) {
1061 final Map.Entry me = (Map.Entry)it.next();
1062 final String nm = (String)me.getKey();
1063 final Object val = me.getValue();
1064 willSerialize(val); //always called even not serializable
1065
1066 if (isVariableSerializable(nm, val)
1067 && (val instanceof java.io.Serializable || val instanceof java.io.Externalizable)) {
1068 s.writeObject(nm);
1069 s.writeObject(val);
1070 }
1071 }
1072 s.writeObject(null); //denote end-of-namespace
1073
1074 //Handles interpreters
1075 for (Iterator it = _ips.entrySet().iterator(); it.hasNext();) {
1076 final Map.Entry me = (Map.Entry)it.next();
1077 final Object ip = me.getValue();
1078 if (ip instanceof SerializableAware) {
1079 s.writeObject((String)me.getKey()); //zslang
1080
1081 ((SerializableAware)ip).write(s,
1082 new SerializableAware.Filter() {
1083 public boolean accept(String name, Object value) {
1084 return isVariableSerializable(name, value);
1085 }
1086 });
1087 }
1088 }
1089 s.writeObject(null); //denote end-of-interpreters
1090 }
1091 private void willSerialize(Collection c) {
1092 if (c != null)
1093 for (Iterator it = c.iterator(); it.hasNext();)
1094 willSerialize(it.next());
1095 }
1096 private void willSerialize(Object o) {
1097 if (o instanceof PageSerializationListener)
1098 ((PageSerializationListener)o).willSerialize(this);
1099 }
1100 private synchronized void readObject(java.io.ObjectInputStream s)
1101 throws java.io.IOException, ClassNotFoundException {
1102 s.defaultReadObject();
1103
1104 init();
1105
1106 final String langnm = (String)s.readObject();
1107 if (langnm != null)
1108 _langdef = LanguageDefinition.lookup(langnm);
1109
1110 _ownerUuid = (String)s.readObject();
1111 //_owner is restored later when sessionDidActivate is called
1112
1113 final String pid = (String)s.readObject();
1114 if (pid != null)
1115 _defparent = fixDefaultParent(_roots, pid);
1116
1117 Serializables.smartRead(s, _attrs);
1118 didDeserialize(_attrs.values());
1119
1120 for (;;) {
1121 final String evtnm = (String)s.readObject();
1122 if (evtnm == null) break; //no more
1123
1124 if (_listeners == null) _listeners = new HashMap();
1125 final Collection ls = Serializables.smartRead(s, (Collection)null);
1126 _listeners.put(evtnm, ls);
1127 didDeserialize(ls);
1128 }
1129
1130 _resolvers = (List)Serializables.smartRead(s, _resolvers); //might be null
1131 didDeserialize(_resolvers);
1132
1133 //handle namespace
1134 initVariables();
1135 for (;;) {
1136 final String nm = (String)s.readObject();
1137 if (nm == null) break; //no more
1138
1139 Object val = s.readObject();
1140 _ns.setVariable(nm, val, true);
1141 didDeserialize(val);
1142 }
1143
1144 fixFellows(_roots);
1145
1146 //Handles interpreters
1147 for (;;) {
1148 final String zslang = (String)s.readObject();
1149 if (zslang == null) break; //no more
1150
1151 ((SerializableAware)getInterpreter(zslang)).read(s);
1152 }
1153 }
1154 private void didDeserialize(Collection c) {
1155 if (c != null)
1156 for (Iterator it = c.iterator(); it.hasNext();)
1157 didDeserialize(it.next());
1158 }
1159 private void didDeserialize(Object o) {
1160 if (o instanceof PageSerializationListener)
1161 ((PageSerializationListener)o).didDeserialize(this);
1162 }
1163 private static boolean isVariableSerializable(String name, Object value) {
1164 return !_nonSerNames.contains(name) && !(value instanceof Component);
1165 }
1166 private final static Set _nonSerNames = new HashSet();
1167 static {
1168 final String[] nms = {"log", "page", "desktop", "pageScope", "desktopScope",
1169 "applicationScope", "requestScope", "spaceOwner",
1170 "session", "sessionScope"};
1171 for (int j = 0; j < nms.length; ++j)
1172 _nonSerNames.add(nms[j]);
1173 }
1174 private final void fixFellows(Collection c) {
1175 for (Iterator it = c.iterator(); it.hasNext();) {
1176 final Component comp = (Component)it.next();
1177 final String compId = comp.getId();
1178 if (!ComponentsCtrl.isAutoId(compId))
1179 addFellow(comp);
1180 if (!(comp instanceof IdSpace))
1181 fixFellows(comp.getChildren()); //recursive
1182 }
1183 }
1184 private static final
1185 Component fixDefaultParent(Collection c, String uuid) {
1186 for (Iterator it = c.iterator(); it.hasNext();) {
1187 Component comp = (Component)it.next();
1188 if (uuid.equals(comp.getUuid()))
1189 return comp; //found
1190
1191 comp = fixDefaultParent(comp.getChildren(), uuid);
1192 if (comp != null) return comp;
1193 }
1194 return null;
1195 }
1196
1197 //-- Object --//
1198 public String toString() {
1199 return "[Page "+_id+']';
1200 }
1201
1202 private class NS extends AbstractNamespace {
1203 private final Map _vars = new HashMap();
1204
1205 //Namespace//
1206 public Component getOwner() {
1207 return null;
1208 }
1209 public Page getOwnerPage() {
1210 return PageImpl.this;
1211 }
1212 public Set getVariableNames() {
1213 return _vars.keySet();
1214 }
1215 public boolean containsVariable(String name, boolean local) {
1216 return _vars.containsKey(name) || _fellows.containsKey(name)
1217 || resolveVariable(name) != null;
1218 }
1219 public Object getVariable(String name, boolean local) {
1220 Object val = _vars.get(name);
1221 if (val != null || _vars.containsKey(name))
1222 return val;
1223
1224 val = _fellows.get(name);
1225 return val != null ? val: resolveVariable(name);
1226 }
1227 public void setVariable(String name, Object value, boolean local) {
1228 _vars.put(name, value);
1229 notifyAdd(name, value);
1230 }
1231 public void unsetVariable(String name, boolean local) {
1232 _vars.remove(name);
1233 notifyRemove(name);
1234 }
1235
1236 public Namespace getParent() {
1237 return null;
1238 }
1239 public void setParent(Namespace parent) {
1240 throw new UnsupportedOperationException();
1241 }
1242 }
1243 }