/* PageImpl.java
Purpose:
Description:
History:
Fri Jun 3 18:17:32 2005, Created by tomyeh
Copyright (C) 2005 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.impl;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.io.Serializables;
import org.zkoss.lang.ClassResolver;
import org.zkoss.lang.ImportedClassResolver;
import org.zkoss.lang.Library;
import org.zkoss.lang.Objects;
import org.zkoss.lang.SimpleClassResolver;
import org.zkoss.lang.Strings;
import org.zkoss.util.CollectionsX;
import org.zkoss.util.DualCollection;
import org.zkoss.web.servlet.Servlets;
import org.zkoss.xel.ExpressionFactory;
import org.zkoss.xel.Function;
import org.zkoss.xel.FunctionMapper;
import org.zkoss.xel.FunctionMapperExt;
import org.zkoss.xel.VariableResolver;
import org.zkoss.xel.XelContext;
import org.zkoss.xel.XelException;
import org.zkoss.xel.util.Evaluators;
import org.zkoss.zk.au.out.AuSetTitle;
import org.zkoss.zk.mesg.MZk;
import org.zkoss.zk.scripting.HierachicalAware;
import org.zkoss.zk.scripting.Interpreter;
import org.zkoss.zk.scripting.InterpreterNotFoundException;
import org.zkoss.zk.scripting.Interpreters;
import org.zkoss.zk.scripting.SerializableAware;
import org.zkoss.zk.ui.AbstractPage;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Richlet;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.ext.Includer;
import org.zkoss.zk.ui.ext.Scope;
import org.zkoss.zk.ui.ext.ScopeListener;
import org.zkoss.zk.ui.ext.Scopes;
import org.zkoss.zk.ui.metainfo.ComponentDefinition;
import org.zkoss.zk.ui.metainfo.ComponentDefinitionMap;
import org.zkoss.zk.ui.metainfo.DefinitionNotFoundException;
import org.zkoss.zk.ui.metainfo.LanguageDefinition;
import org.zkoss.zk.ui.metainfo.PageDefinition;
import org.zkoss.zk.ui.metainfo.ZScript;
import org.zkoss.zk.ui.sys.Attributes;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.sys.DesktopCtrl;
import org.zkoss.zk.ui.sys.ExecutionCtrl;
import org.zkoss.zk.ui.sys.ExecutionsCtrl;
import org.zkoss.zk.ui.sys.PageConfig;
import org.zkoss.zk.ui.sys.PageCtrl;
import org.zkoss.zk.ui.sys.PageRenderer;
import org.zkoss.zk.ui.sys.UiEngine;
import org.zkoss.zk.ui.sys.WebAppCtrl;
import org.zkoss.zk.ui.util.Condition;
import org.zkoss.zk.ui.util.PageActivationListener;
import org.zkoss.zk.ui.util.PageSerializationListener;
/**
* An implementation of {@link Page} and {@link PageCtrl}.
* Refer to them for more details.
*
* Note: though {@link PageImpl} is serializable, it is designed
* to work with Web container to enable the serialization of sessions.
* It is not suggested to serialize and deserialize it directly since
* many fields might be lost.
*
*
On the other hand, it is OK to serialize and deserialize
* {@link Component}.
*
*
Implementation Notes:
* It is not thread-safe because it is protected by the spec:
* at most one thread can access a page and all its components at the same time.
*
* @author tomyeh
*/
public class PageImpl extends AbstractPage implements java.io.Serializable {
private static final Logger log = LoggerFactory.getLogger(PageImpl.class);
private static final long serialVersionUID = 20110726L;
/** The component that includes this page, or null if not included. */
private transient Component _owner;
/** Used to restore _owner. */
private transient String _ownerUuid;
private transient Desktop _desktop;
private String _id = "", _uuid;
private String _title = "", _style = "", _viewport = "auto";
private String _path;
private String _zslang;
/** A list of deferred zscript [Component parent, {@link ZScript}]. */
private List _zsDeferred;
/** A map of attributes. */
private transient SimpleScope _attrs;
/** A map of event listener: Map(evtnm, List(EventListener)). */
private transient Map>> _listeners;
/** The reason to store it is PageDefinition is not serializable. */
private final ComponentDefinitionMap _compdefs;
/** The reason to store it is PageDefinition is not serializable. */
private transient LanguageDefinition _langdef;
/** The header tags. */
private String _hdbfr = "", _hdaft = "";
/** The response headers. */
private Collection _hdres;
/** The root attributes. */
private String _rootAttrs = "";
private String _contentType, _docType, _firstLine, _wgtcls;
private Boolean _cacheable;
private Boolean _autoTimeout;
/** The expression factory (ExpressionFactory).*/
private Class extends ExpressionFactory> _expfcls;
/** A map of interpreters Map(String zslang, Interpreter ip). */
private transient Map _ips;
/** The mapper representing all mappers being added to this page. */
private final FunctionMapper _mapper = new PageFuncMapper();
/** A list of {@link FunctionMapper}. */
private transient List _mappers;
/** A list of {@link VariableResolver}. */
private transient List _resolvers;
/** A list of class's name pattern (String). */
private ClassResolver _clsresolver;
/** Whether _clsresolver is shared with other pages. */
private boolean _clsresolverShared;
private boolean _complete;
private List _impclss;
/** Constructs a page by giving the page definition.
*
* Note: when a page is constructed, it doesn't belong to a desktop
* yet. Caller has to invoke {@link #init} to complete
* the creation of a page.
* Why two phase? Constructor could be called before execution
* is activated, but {@link #init} must be called in an execution.
*
*
Also note that {@link #getId} and {@link #getTitle}
* are not ready until {@link #init} is called.
*
* @param pgdef the page definition (never null).
*/
public PageImpl(PageDefinition pgdef) {
constr(pgdef.getLanguageDefinition(),
pgdef.getRequestPath(), pgdef.getZScriptLanguage());
_compdefs = pgdef.getComponentDefinitionMap();
_clsresolver = pgdef.getImportedClassResolver();
_clsresolverShared = true;
_impclss = pgdef.getImportedClasses();
//NOTE: don't store pgdef since it is not serializable
}
/** Constructs a page without page definition and richlet.
*
* @param langdef the language definition (never null)
* @param compdefs the component definition map.
* If null, an empty map is assumed.
* @param path the request path. If null, empty is assumed.
* @param zslang the zscript language. If null, "Java" is assumed.
*/
public PageImpl(LanguageDefinition langdef,
ComponentDefinitionMap compdefs, String path, String zslang) {
constr(langdef, path, zslang);
_compdefs = compdefs != null ? compdefs:
new ComponentDefinitionMap(
_langdef.getComponentDefinitionMap().isCaseInsensitive());
_clsresolver = new SimpleClassResolver();
}
/** Constructs a page with another page as instance
* @since 6.0.0
*/
public PageImpl(Page ref) {
this(ref.getLanguageDefinition(), ref.getComponentDefinitionMap(),
ref.getRequestPath(), ref.getZScriptLanguage());
}
/** Constructs a page by specifying a richlet.
*
*
Note: when a page is constructed, it doesn't belong to a desktop
* yet. Caller has to invoke {@link #init} to complete
* the creation of a page.
*
*
Also note that {@link #getId} and {@link #getTitle}
* are not ready until {@link #init} is called.
*
* @param richlet the richlet to serve this page.
* @param path the request path, or null if not available
*/
public PageImpl(Richlet richlet, String path) {
constr(richlet.getLanguageDefinition(), path, null);
_compdefs = new ComponentDefinitionMap(
_langdef.getComponentDefinitionMap().isCaseInsensitive());
_clsresolver = new SimpleClassResolver();
}
private void constr(LanguageDefinition langdef, String path, String zslang) {
init();
_langdef = langdef;
_path = path != null ? path: "";
_zslang = zslang != null ? zslang: "Java";
}
/** Initialized the page when constructed or deserialized.
*/
protected void init() {
_ips = new LinkedHashMap(2);
_attrs = new SimpleScope(this);
}
/** Returns the UI engine.
*/
private final UiEngine getUiEngine() {
return ((WebAppCtrl)_desktop.getWebApp()).getUiEngine();
}
//-- Page --//
private final Execution getExecution() {
return _desktop != null ? _desktop.getExecution():
Executions.getCurrent();
}
public final FunctionMapper getFunctionMapper() {
return _mapper;
}
public boolean addFunctionMapper(FunctionMapper mapper) {
if (mapper == null)
return false;
if (_mappers == null)
_mappers = new LinkedList();
else if (_mappers.contains(mapper))
return false;
_mappers.add(0, mapper); //FILO order
return true;
}
public boolean removeFunctionMapper(FunctionMapper mapper) {
return _mappers != null && _mappers.remove(mapper);
}
public boolean hasFunctionMapper(FunctionMapper mapper) {
return _mappers != null && _mappers.contains(mapper);
}
public String getRequestPath() {
return _path;
}
public final String getId() {
return _id;
}
public final String getUuid() {
return _uuid;
}
public void setId(String id) {
if (_desktop != null && _desktop.getPages().contains(this))
throw new UiException("ID cannot be changed after initialized");
_id = id != null ? id: "";
//No need to update client since it is allowed only before init(...)
}
public String getTitle() {
return _title;
}
public void setTitle(String title) {
if (title == null) title = "";
if (!_title.equals(title)) {
_title = title;
if (_desktop != null) {
final Execution exec = getExecution();
if (_title.length() > 0) {
_title = (String)exec.evaluate(this, _title, String.class);
if (_title == null) _title = "";
}
if (exec.isAsyncUpdate(this))
getUiEngine().addResponse(new AuSetTitle(_title));
}
}
}
public String getStyle() {
return _style;
}
public void setStyle(String style) {
if (style == null) style = "";
if (!_style.equals(style)) {
_style = style;
if (_desktop != null) {
final Execution exec = getExecution();
if (_style.length() > 0) {
_style = (String)exec.evaluate(this, _style, String.class);
if (_style == null) _style = "";
}
//FUTURE: might support the change of style dynamically
}
}
}
public String getViewport() {
return _viewport;
}
public void setViewport(String viewport) {
if (viewport == null) viewport = "auto";
if (!_viewport.equals(viewport)) {
_viewport = viewport;
if (_desktop != null) {
final Execution exec = getExecution();
if (_viewport.length() > 0) {
_viewport = (String)exec.evaluate(this, _viewport, String.class);
if (_viewport == null) _viewport = "auto";
}
}
}
}
public Map getAttributes(int scope) {
switch (scope) {
case DESKTOP_SCOPE:
if (_desktop != null)
return _desktop.getAttributes();
break;
case SESSION_SCOPE:
if (_desktop != null)
return _desktop.getSession().getAttributes();
break;
case APPLICATION_SCOPE:
if (_desktop != null)
return _desktop.getWebApp().getAttributes();
case PAGE_SCOPE:
return _attrs.getAttributes();
case REQUEST_SCOPE:
final Execution exec = getExecution();
if (exec != null)
return exec.getAttributes();
}
return Collections.emptyMap();
}
public Object getAttribute(String name, int scope) {
return getAttributes(scope).get(name);
}
public boolean hasAttribute(String name, int scope) {
return getAttributes(scope).containsKey(name);
}
public Object setAttribute(String name, Object value, int scope) {
final Map attrs = getAttributes(scope);
if (attrs == Collections.EMPTY_MAP)
throw new IllegalStateException("This component doesn't belong to any ID space: "+this);
return attrs.put(name, value);
}
public Object removeAttribute(String name, int scope) {
final Map attrs = getAttributes(scope);
if (attrs == Collections.EMPTY_MAP)
throw new IllegalStateException("This component doesn't belong to any ID space: "+this);
return attrs.remove(name);
}
public Map getAttributes() {
return _attrs.getAttributes();
}
public Object getAttribute(String name) {
return _attrs.getAttribute(name);
}
public boolean hasAttribute(String name) {
return _attrs.hasAttribute(name);
}
public Object setAttribute(String name, Object value) {
return _attrs.setAttribute(name, value);
}
public Object removeAttribute(String name) {
return _attrs.removeAttribute(name);
}
public Object getAttribute(String name, boolean recurse) {
Object val = getAttribute(name);
return val != null || !recurse || hasAttribute(name) ? val:
_desktop != null ? _desktop.getAttribute(name, true): null;
}
public boolean hasAttribute(String name, boolean recurse) {
return hasAttribute(name)
|| (recurse && _desktop != null && _desktop.hasAttribute(name, true));
}
public Object setAttribute(String name, Object value, boolean recurse) {
if (recurse && !hasAttribute(name)) {
if (_desktop != null) {
if (_desktop.hasAttribute(name, true))
return _desktop.setAttribute(name, value, true);
}
}
return setAttribute(name, value);
}
public Object removeAttribute(String name, boolean recurse) {
if (recurse && !hasAttribute(name)) {
if (_desktop != null) {
if (_desktop.hasAttribute(name, true))
return _desktop.removeAttribute(name, true);
}
return null;
}
return removeAttribute(name);
}
public Object getAttributeOrFellow(String name, boolean recurse) {
Object val = getAttribute(name);
if (val != null || hasAttribute(name))
return val;
val = getFellowIfAny(name);
if (val != null)
return val;
return recurse && _desktop != null ?
_desktop.getAttribute(name, true): null;
}
public boolean hasAttributeOrFellow(String name, boolean recurse) {
return hasAttribute(name) || hasFellow(name)
|| (recurse && _desktop != null && _desktop.hasAttribute(name, true));
}
public boolean addScopeListener(ScopeListener listener) {
return _attrs.addScopeListener(listener);
}
public boolean removeScopeListener(ScopeListener listener) {
return _attrs.removeScopeListener(listener);
}
public void invalidate() {
getUiEngine().addInvalidate(this);
}
public boolean addClassResolver(ClassResolver resolver) {
//Currently we support only SimpleClassResolver and ImportedClassResolver
//but it is good enough
if (resolver == null)
return false;
if (!(resolver instanceof SimpleClassResolver)) {
if (!(resolver instanceof ImportedClassResolver))
throw new UnsupportedOperationException("Only ImportedClassResolver supported");
if (_clsresolver instanceof SimpleClassResolver) {
_clsresolver = resolver;
_clsresolverShared = true;
} else {
ImportedClassResolver cur = (ImportedClassResolver)_clsresolver;
if (_clsresolverShared) {
ImportedClassResolver newcr = new ImportedClassResolver();
newcr.addAll(cur);
_clsresolver = newcr;
_clsresolverShared = false;
}
cur.addAll((ImportedClassResolver)resolver);
}
}
return true;
}
public Class> resolveClass(String clsnm) throws ClassNotFoundException {
try {
return _clsresolver.resolveClass(clsnm);
} catch (ClassNotFoundException ex) {
final Class> cls = getZScriptClass(clsnm);
if (cls != null)
return cls;
throw ex;
}
}
public Class> getZScriptClass(String clsnm) {
for (Interpreter ip: getLoadedInterpreters()) {
Class> cls = ip.getClass(clsnm);
if (cls != null)
return cls;
}
return null; //Since ZK 6, we don't look for the current thread's class loader
}
public Function getZScriptFunction(String name, Class[] argTypes) {
for (Interpreter ip: getLoadedInterpreters()) {
Function mtd = ip.getFunction(name, argTypes);
if (mtd != null)
return mtd;
}
return null;
}
public Function getZScriptFunction(
Component comp, String name, Class[] argTypes) {
for (Interpreter ip: getLoadedInterpreters()) {
Function mtd =
ip instanceof HierachicalAware ?
((HierachicalAware)ip).getFunction(comp, name, argTypes):
(ip).getFunction(name, argTypes);
if (mtd != null)
return mtd;
}
return null;
}
public Object getZScriptVariable(String name) {
for (Interpreter ip: getLoadedInterpreters()) {
final Object val = ip.getVariable(name);
if (val != null)
return val;
}
return null;
}
public Object getZScriptVariable(Component comp, String name) {
for (Interpreter ip: getLoadedInterpreters()) {
final Object val = ip instanceof HierachicalAware ?
((HierachicalAware)ip).getVariable(comp, name):
(ip).getVariable(name);
if (val != null)
return val;
}
return null;
}
public Object getXelVariable(String name) {
return getXelVariable(null, null, name, false);
}
public Object getXelVariable(
XelContext ctx, Object base, Object name, boolean ignoreExec) {
if (!ignoreExec) {
final Execution exec = getExecution();
if (exec != null)
return Evaluators.resolveVariable(
ctx, exec.getVariableResolver(), base, name);
//note: ExecutionResolver will call back this method
}
if (_resolvers != null) {
for (Iterator it = CollectionsX.comodifiableIterator(_resolvers);
it.hasNext();) {
Object o = Evaluators.resolveVariable(ctx, it.next(), base, name);
if (o != null)
return o;
}
}
return null;
}
public boolean addVariableResolver(VariableResolver resolver) {
if (resolver == null)
return false;
if (_resolvers == null)
_resolvers = new LinkedList();
else if (_resolvers.contains(resolver))
return false;
_resolvers.add(0, resolver); //FILO order
return true;
}
public boolean removeVariableResolver(VariableResolver resolver) {
return _resolvers != null && _resolvers.remove(resolver);
}
public boolean hasVariableResolver(VariableResolver resolver) {
return _resolvers != null && _resolvers.contains(resolver);
}
public boolean addEventListener(String evtnm, EventListener extends Event> listener) {
if (evtnm == null || listener == null)
throw new IllegalArgumentException("null");
if (!Events.isValid(evtnm))
throw new IllegalArgumentException("Invalid event name: "+evtnm);
if (_listeners == null)
_listeners = new HashMap>>(2);
List> l = _listeners.get(evtnm);
if (l != null) {
if (duplicateListenerIgnored()) {
for (EventListener extends Event> li: l) {
if (listener.equals(li))
return false;
}
}
} else {
_listeners.put(evtnm, l = new LinkedList>());
}
l.add(listener);
return true;
}
public boolean removeEventListener(String evtnm, EventListener extends Event> listener) {
if (evtnm == null || listener == null)
throw new NullPointerException();
if (_listeners != null) {
final List> ls = _listeners.get(evtnm);
if (ls != null) {
for (Iterator> it = ls.iterator(); it.hasNext();) {
final EventListener extends Event> li = it.next();
if (listener.equals(li)) {
if (ls.size() == 1)
_listeners.remove(evtnm);
else
it.remove();
return true;
}
}
}
}
return false;
}
private static boolean duplicateListenerIgnored() {
if (dupListenerIgnored == null)
dupListenerIgnored = Boolean.valueOf(
"true".equals(Library.getProperty("org.zkoss.zk.ui.EventListener.duplicateIgnored")));
return dupListenerIgnored.booleanValue();
}
private static Boolean dupListenerIgnored;
public boolean isComplete() {
return _complete;
}
public void setComplete(boolean complete) {
_complete = complete;
}
//-- PageCtrl --//
public void preInit() {
if (_desktop != null)
throw new IllegalStateException("init twice");
final Execution exec = Executions.getCurrent();
_desktop = exec.getDesktop();
if (_desktop == null)
throw new IllegalArgumentException("null desktop");
_desktop.getWebApp().getConfiguration().init(this);
}
public void init(PageConfig config) {
final Execution exec = Executions.getCurrent();
if (((ExecutionCtrl)exec).isRecovering()) {
final String uuid = config.getUuid(), id = config.getId();
if (uuid == null || id == null)
throw new IllegalArgumentException("both id and uuid are required in recovering");
_uuid = uuid;
_id = id;
} else {
_uuid = ((DesktopCtrl)_desktop).getNextUuid(this);
if (_id == null || _id.length() == 0)
_id = config.getId();
if (_id != null)
_id = (String)exec.evaluate(this, _id, String.class);
if (_id == null) {
_id = "";
} else if (_id.length() != 0) {
final String INVALID = ".&\\%";
if (Strings.anyOf(_id, INVALID, 0) < _id.length())
throw new IllegalArgumentException("Invalid page ID: "+_id+". Invalid characters: "+INVALID);
}
}
//Note: the evaluation order was changed since 5.0.2
((DesktopCtrl)_desktop).addPage(this);
//add page before evaluate title and others
String s;
if (_title.length() == 0) {
s = config.getTitle();
if (s != null) setTitle(s);
}
if (_style.length() == 0) {
s = config.getStyle();
if (s != null) setStyle(s);
}
if ("auto".equals(_viewport)) {
s = config.getViewport();
if (s != null) setViewport(s);
}
s = config.getBeforeHeadTags();
if (s != null) _hdbfr = s;
s = config.getAfterHeadTags();
if (s != null) _hdaft = s;
_hdres = config.getResponseHeaders();
if (_hdres.isEmpty()) _hdres = null;
}
public void destroy() {
super.destroy();
try {
if (_ips != null) {
final List ips = new ArrayList(_ips.values());
_ips.clear();
_ips = null; //not just clear since it is better to NPE than memory leak
for (Interpreter ip: ips) {
try {
ip.destroy();
} catch (Throwable ex) {
log.warn("Failed to destroy "+ip, ex);
}
}
}
} catch (Throwable ex) { //avoid racing
log.warn("Failed to clean up interpreters of "+this, ex);
}
//theoretically, the following is not necessary, but, to be safe...
_desktop = null;
_owner = null;
_listeners = null;
_resolvers = null;
_mappers = null;
_attrs.getAttributes().clear();
}
public boolean isAlive() {
return _ips != null;
}
public String getBeforeHeadTags() {
return _hdbfr;
}
public String getAfterHeadTags() {
return _hdaft;
}
public void addBeforeHeadTags(String tags) {
if (tags != null && tags.length() > 0)
_hdbfr += '\n' + tags;
}
public void addAfterHeadTags(String tags) {
if (tags != null && tags.length() > 0)
_hdaft += '\n' + tags;
}
public Collection getResponseHeaders() {
if (_hdres != null)
return _hdres;
return Collections.emptyList();
}
public String getRootAttributes() {
return _rootAttrs;
}
public void setRootAttributes(String rootAttrs) {
_rootAttrs = rootAttrs != null ? rootAttrs: "";
}
public String getContentType() {
return _contentType;
}
public void setContentType(String contentType) {
_contentType = contentType;
}
public String getWidgetClass() {
return _wgtcls;
}
public void setWidgetClass(String wgtcls) {
_wgtcls = wgtcls != null && wgtcls.length() > 0 ? wgtcls: null;
}
public String getDocType() {
return _docType;
}
public void setDocType(String docType) {
_docType = docType;
}
public String getFirstLine() {
return _firstLine;
}
public void setFirstLine(String firstLine) {
_firstLine = firstLine;
}
public Boolean getCacheable() {
return _cacheable;
}
public void setCacheable(Boolean cacheable) {
_cacheable = cacheable;
}
public Boolean getAutomaticTimeout() {
return _autoTimeout;
}
public void setAutomaticTimeout(Boolean autoTimeout) {
_autoTimeout = autoTimeout;
}
public final Desktop getDesktop() {
return _desktop;
}
public void redraw(Writer out)
throws IOException {
String ctl;
final Execution exec = getExecution();
final boolean au = exec.isAsyncUpdate(null);
if (!au && !exec.isIncluded()
&& ((ctl=ExecutionsCtrl.getPageRedrawControl(exec)) == null
|| "desktop".equals(ctl))) {
boolean ie7compat;
if (!au && ((ie7compat = shallIE7Compatible()) || !shallDisableAutoCompatible()))
try {
if (exec.getBrowser("ie") >= 8
&& !exec.containsResponseHeader("X-UA-Compatible")) {
if (ie7compat) {
exec.setResponseHeader("X-UA-Compatible", "IE=EmulateIE7");
} else {
double[] ieCompatibilityInfo = Servlets.getIECompatibilityInfo((ServletRequest) exec.getNativeRequest());
if(ieCompatibilityInfo != null) {
if(ieCompatibilityInfo[0] != ieCompatibilityInfo[2]) {
exec.addResponseHeader("X-UA-Compatible", "IE=" + (int)ieCompatibilityInfo[2]);
}
}
}
}
} catch (Throwable ex) { //ignore (it might not be allowed)
}
//FUTURE: Consider if config.isKeepDesktopAcrossVisits() implies cacheable
//Why yes: the client doesn't need to ask the server for updated content
//Why no: browsers seems fail to handle DHTML correctly (when BACK to
//a DHTML page), so it is better to let the server handle cache, if any
final boolean cacheable =
_cacheable != null ? _cacheable.booleanValue():
_desktop.getDevice().isCacheable();
if (!cacheable) {
//Bug 1520444
exec.setResponseHeader("Pragma", "no-cache");
exec.addResponseHeader("Cache-Control", "no-cache");
exec.addResponseHeader("Cache-Control", "no-store");
//exec.addResponseHeader("Cache-Control", "private");
//exec.addResponseHeader("Cache-Control", "max-age=0");
//exec.addResponseHeader("Cache-Control", "s-maxage=0");
//exec.addResponseHeader("Cache-Control", "must-revalidate");
//exec.addResponseHeader("Cache-Control", "proxy-revalidate");
//exec.addResponseHeader("Cache-Control", "post-check=0");
//exec.addResponseHeader("Cache-Control", "pre-check=0");
exec.setResponseHeader("Expires", "-1");
exec.setAttribute(Attributes.NO_CACHE, Boolean.TRUE);
//so HtmlPageRenderers.outLangJavaScripts generates JS's keepDesktop correctly
}
if (_hdres != null)
for (Object[] vals: _hdres) {
final String nm = (String)vals[0];
final Object val = vals[1];
final boolean add = ((Boolean)vals[2]).booleanValue();
if (val instanceof Date) {
if (add) exec.addResponseHeader(nm, (Date)val);
else exec.setResponseHeader(nm, (Date)val);
} else {
if (add) exec.addResponseHeader(nm, (String)val);
else exec.setResponseHeader(nm, (String)val);
}
}
}
final PageRenderer renderer = (PageRenderer)
exec.getAttribute(Attributes.PAGE_RENDERER);
final Object oldrendering =
exec.setAttribute(Attributes.PAGE_RENDERING, Boolean.TRUE);
try {
(renderer != null ? renderer: _langdef.getPageRenderer())
.render(this, out);
} finally {
if (oldrendering != null)
exec.setAttribute(Attributes.PAGE_RENDERING, oldrendering);
else
exec.removeAttribute(Attributes.PAGE_RENDERING);
}
}
private static Boolean _ie7compat;
private static Boolean _ieAutoCompat;
private static boolean shallIE7Compatible() {
if (_ie7compat == null)
_ie7compat = Boolean.valueOf("true".equals(
Library.getProperty("org.zkoss.zk.ui.EmulateIE7")));
return _ie7compat.booleanValue();
}
private static boolean shallDisableAutoCompatible() {
if (_ieAutoCompat == null)
_ieAutoCompat = Boolean.valueOf("true".equals(
Library.getProperty("org.zkoss.zk.ui.IEAutoCompatible.disabled")));
return _ieAutoCompat.booleanValue();
}
public void interpret(String zslang, String script, Scope scope) {
if (script != null && script.length() > 0) //optimize for better performance
getInterpreter(zslang).interpret(script, scope);
}
public Interpreter getInterpreter(String zslang) {
zslang = (zslang != null ? zslang: _zslang).toLowerCase(java.util.Locale.ENGLISH);
Interpreter ip = _ips.get(zslang);
if (ip == null) {
if (_desktop != null //might be null, if deserialized
&& !_desktop.getWebApp().getConfiguration().isZScriptEnabled())
throw new UiException("zscript is not allowed since is configured");
ip = Interpreters.newInterpreter(zslang, this);
_ips.put(zslang, ip);
//set first to avoid dead loop if script calls interpret again
String script = _langdef.getInitScript(zslang);
if (script != null) {
//Bug ZK-1498: also add directive to zscript
//Bug ZK-1514: _impclss may be null
if (_impclss != null && !_impclss.isEmpty() && "java".equals(zslang)) {
StringBuilder sb = new StringBuilder();
for (String name : _impclss)
sb.append("\nimport ").append(name).append(";");
script += sb.toString();
sb = null;
}
ip.interpret(script, this);
}
}
//evaluate deferred zscripts, if any
evalDeferredZScripts(ip, zslang);
return ip;
}
public Collection getLoadedInterpreters() {
if (_ips != null)
return _ips.values();
return Collections.emptyList(); //just in case
}
public String getZScriptLanguage() {
return _zslang;
}
public void setZScriptLanguage(String zslang)
throws InterpreterNotFoundException {
if (!Objects.equals(zslang, _zslang)) {
if (!Interpreters.exists(zslang))
throw new InterpreterNotFoundException(zslang, MZk.NOT_FOUND, zslang);
_zslang = zslang;
}
}
public void addDeferredZScript(Component parent, ZScript zscript) {
if (zscript != null) {
if (_zsDeferred == null)
_zsDeferred = new LinkedList();
_zsDeferred.add(new Object[] {parent, zscript});
}
}
/** Evaluates the deferred zscript.
* It is called when the interpreter is loaded
*/
private void evalDeferredZScripts(Interpreter ip, String zslang) {
if (_zsDeferred != null) {
for (Iterator it = _zsDeferred.iterator(); it.hasNext();) {
final Object[] zsInfo = it.next();
final ZScript zscript = (ZScript)zsInfo[1];
String targetlang = zscript.getLanguage();
if (targetlang == null)
targetlang = _zslang; //use default
if (targetlang.equalsIgnoreCase(zslang)) { //case insensitive
it.remove(); //done
final Component parent = (Component)zsInfo[0];
if (parent == null || parent.getPage() == this) {
final Scope scope =
Scopes.beforeInterpret(parent != null ? (Scope)parent: this);
try {
ip.interpret(zscript.getContent(this, parent), scope);
} finally {
Scopes.afterInterpret();
}
}
}
}
if (_zsDeferred.isEmpty())
_zsDeferred = null;
}
}
private boolean isEffective(Condition cond, Component comp) {
return comp != null ? cond.isEffective(comp): cond.isEffective(this);
}
public boolean isListenerAvailable(String evtnm) {
if (_listeners != null) {
final List> ls = _listeners.get(evtnm);
return ls != null && !ls.isEmpty();
}
return false;
}
/** @deprecated As of release 6.0, replaced with {@link #getEventListeners}.
*/
public Iterator> getListenerIterator(String evtnm) {
if (_listeners != null) {
final List> l = _listeners.get(evtnm);
if (l != null)
return CollectionsX.comodifiableIterator(l);
}
return CollectionsX.emptyIterator();
}
public Iterable> getEventListeners(String evtnm) {
if (_listeners != null) {
final List> l = _listeners.get(evtnm);
if (l != null)
return new Iterable>() {
public Iterator> iterator() {
return CollectionsX.comodifiableIterator(l);
}
};
}
return CollectionsX.emptyIterable();
}
public final Component getOwner() {
return _owner;
}
public final void setOwner(Component comp) {
if (_owner != null)
throw new IllegalStateException("owner can be set only once");
_owner = comp;
if (_owner instanceof Includer)
((Includer)_owner).setChildPage(this);
}
public void sessionWillPassivate(Desktop desktop) {
for (Component root = getFirstRoot(); root != null; root = root.getNextSibling())
((ComponentCtrl)root).sessionWillPassivate(this);
willPassivate(_attrs.getAttributes().values());
willPassivate(_attrs.getListeners());
if (_listeners != null)
for (Iterator>> it = CollectionsX.comodifiableIterator(_listeners.values());
it.hasNext();)
willPassivate(it.next());
willPassivate(_resolvers);
willPassivate(_mappers);
}
public void sessionDidActivate(Desktop desktop) {
_desktop = desktop;
if (_ownerUuid != null) {
setOwner(_desktop.getComponentByUuid(_ownerUuid));
_ownerUuid = null;
}
for (Component root = getFirstRoot(); root != null; root = root.getNextSibling())
((ComponentCtrl)root).sessionDidActivate(this);
didActivate(_attrs.getAttributes().values());
didActivate(_attrs.getListeners());
if (_listeners != null)
for (Iterator>> it = CollectionsX.comodifiableIterator(_listeners.values());
it.hasNext();)
didActivate(it.next());
didActivate(_resolvers);
didActivate(_mappers);
}
private void willPassivate(Collection c) {
if (c != null)
for (Iterator it = c.iterator(); it.hasNext();)
willPassivate(it.next());
}
private void willPassivate(Object o) {
if (o instanceof PageSerializationListener)
((PageActivationListener)o).willPassivate(this);
}
private void didActivate(Collection c) {
if (c != null)
for (Iterator it = c.iterator(); it.hasNext();)
didActivate(it.next());
}
private void didActivate(Object o) {
if (o instanceof PageSerializationListener)
((PageActivationListener)o).didActivate(this);
}
public LanguageDefinition getLanguageDefinition() {
return _langdef;
}
public ComponentDefinitionMap getComponentDefinitionMap() {
return _compdefs;
}
public ComponentDefinition getComponentDefinition(String name, boolean recurse) {
final ComponentDefinition compdef = _compdefs.get(name);
if (!recurse || compdef != null)
return compdef;
try {
return _langdef.getComponentDefinition(name);
} catch (DefinitionNotFoundException ex) {
}
return null;
}
public ComponentDefinition getComponentDefinition(Class extends Component> cls, boolean recurse) {
final ComponentDefinition compdef = _compdefs.get(cls);
if (!recurse || compdef != null)
return compdef;
try {
return _langdef.getComponentDefinition(cls);
} catch (DefinitionNotFoundException ex) {
}
return null;
}
public Class extends ExpressionFactory> getExpressionFactoryClass() {
return _expfcls;
}
public void setExpressionFactoryClass(Class extends ExpressionFactory> expfcls) {
if (expfcls != null && !ExpressionFactory.class.isAssignableFrom(expfcls))
throw new IllegalArgumentException(expfcls+" must implement "+ExpressionFactory.class);
_expfcls = expfcls;
}
//-- Serializable --//
//NOTE: they must be declared as private
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeObject(_langdef != null ? _langdef.getName(): null);
s.writeObject(_owner != null ? _owner.getUuid(): null);
final Map attrs = _attrs.getAttributes();
willSerialize(attrs.values());
Serializables.smartWrite(s, attrs);
final List lns = _attrs.getListeners();
willSerialize(lns);
Serializables.smartWrite(s, lns);
if (_listeners != null)
for (Map.Entry>> me: _listeners.entrySet()) {
s.writeObject(me.getKey());
final List> ls = me.getValue();
willSerialize(ls);
Serializables.smartWrite(s, ls);
}
s.writeObject(null);
willSerialize(_resolvers);
Serializables.smartWrite(s, _resolvers);
willSerialize(_mappers);
Serializables.smartWrite(s, _mappers);
//Handles interpreters
for (Map.Entry me: _ips.entrySet()) {
final Interpreter ip = me.getValue();
if (ip instanceof SerializableAware) {
s.writeObject(me.getKey()); //zslang
((SerializableAware)ip).write(s, null);
}
}
s.writeObject(null); //denote end-of-interpreters
}
private void willSerialize(Collection c) {
if (c != null)
for (Iterator it = c.iterator(); it.hasNext();)
willSerialize(it.next());
}
private void willSerialize(Object o) {
if (o instanceof PageSerializationListener)
((PageSerializationListener)o).willSerialize(this);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
init();
final String langnm = (String)s.readObject();
if (langnm != null)
_langdef = LanguageDefinition.lookup(langnm);
_ownerUuid = (String)s.readObject();
//_owner is restored later when sessionDidActivate is called
final Map attrs = _attrs.getAttributes();
Serializables.smartRead(s, attrs);
final List lns = _attrs.getListeners();
Serializables.smartRead(s, lns);
for (;;) {
final String evtnm = (String)s.readObject();
if (evtnm == null) break; //no more
if (_listeners == null) _listeners = new HashMap>>();
final List> ls = Serializables.smartRead(s, (List>)null);
_listeners.put(evtnm, ls);
}
_resolvers = Serializables.smartRead(s, _resolvers); //might be null
_mappers = Serializables.smartRead(s, _mappers); //might be null
//Handles interpreters
for (;;) {
final String zslang = (String)s.readObject();
if (zslang == null) break; //no more
((SerializableAware)getInterpreter(zslang)).read(s);
}
//didDeserialize
didDeserialize(attrs.values());
didDeserialize(lns);
didDeserialize(_resolvers);
didDeserialize(_mappers);
if (_listeners != null)
for (List> ls: _listeners.values())
didDeserialize(ls);
}
private void didDeserialize(Collection c) {
if (c != null)
for (Iterator it = c.iterator(); it.hasNext();)
didDeserialize(it.next());
}
private void didDeserialize(Object o) {
if (o instanceof PageSerializationListener)
((PageSerializationListener)o).didDeserialize(this);
}
//-- Object --//
public String toString() {
return "[Page "+(_id.length() > 0 ? _id: _uuid)+']';
}
private class PageFuncMapper
implements FunctionMapper, FunctionMapperExt, java.io.Serializable {
public Function resolveFunction(String prefix, String name)
throws XelException {
if (_mappers != null) {
for (Iterator it = CollectionsX.comodifiableIterator(_mappers);
it.hasNext();) {
final Function f = it.next().resolveFunction(prefix, name);
if (f != null)
return f;
}
}
return null;
}
public Collection getClassNames() {
Collection coll = null;
if (_mappers != null) {
for (Iterator it = CollectionsX.comodifiableIterator(_mappers);
it.hasNext();) {
final FunctionMapper mapper = (FunctionMapper)it.next();
if (mapper instanceof FunctionMapperExt)
coll = combine(coll,
((FunctionMapperExt)mapper).getClassNames());
}
}
if (coll != null)
return coll;
return Collections.emptyList();
}
public Class resolveClass(String name) throws XelException {
if (_mappers != null) {
for (Iterator it = CollectionsX.comodifiableIterator(_mappers);
it.hasNext();) {
final FunctionMapper mapper = (FunctionMapper)it.next();
if (mapper instanceof FunctionMapperExt) {
final Class c =
((FunctionMapperExt)mapper).resolveClass(name);
if (c != null)
return c;
}
}
}
return null;
}
}
private static Collection combine(Collection first, Collection second) {
return DualCollection.combine(
first != null && !first.isEmpty() ? first: null,
second != null && !second.isEmpty() ? second: null);
}
}