zk/src/main/java/org/zkoss/zk/ui/util/Configuration.java
/* Configuration.java
Purpose:
Description:
History:
Sun Mar 26 16:06:56 2006, Created by tomyeh
Copyright (C) 2006 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.util;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Objects;
import org.zkoss.lang.PotentialDeadLockException;
import org.zkoss.util.FastReadArray;
import org.zkoss.util.WaitLock;
import org.zkoss.web.theme.ThemeRegistry;
import org.zkoss.web.theme.ThemeResolver;
import org.zkoss.xel.ExpressionFactory;
import org.zkoss.xel.Expressions;
import org.zkoss.xel.VariableResolver;
import org.zkoss.zk.au.AuDecoder;
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.RichletConfig;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.ShadowElement;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WebApp;
import org.zkoss.zk.ui.WebApps;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventThreadCleanup;
import org.zkoss.zk.ui.event.EventThreadInit;
import org.zkoss.zk.ui.event.EventThreadResume;
import org.zkoss.zk.ui.event.EventThreadSuspend;
import org.zkoss.zk.ui.impl.EventInterceptors;
import org.zkoss.zk.ui.impl.MultiComposer;
import org.zkoss.zk.ui.impl.RichletConfigImpl;
import org.zkoss.zk.ui.metainfo.NamespaceParser;
import org.zkoss.zk.ui.sys.DesktopCacheProvider;
import org.zkoss.zk.ui.sys.DiskFileItemFactory;
import org.zkoss.zk.ui.sys.FailoverManager;
import org.zkoss.zk.ui.sys.IdGenerator;
import org.zkoss.zk.ui.sys.PropertiesRenderer;
import org.zkoss.zk.ui.sys.SEORenderer;
import org.zkoss.zk.ui.sys.SessionCache;
import org.zkoss.zk.ui.sys.UiEngine;
import org.zkoss.zk.ui.sys.UiFactory;
import org.zkoss.zk.ui.sys.WebAppCtrl;
import org.zkoss.zk.ui.sys.WebAppFactory;
/**
* The ZK configuration.
*
* <p>To retrieve the current configuration, use
* {@link org.zkoss.zk.ui.WebApp#getConfiguration}.
*
* <p>Note: A {@link Configuration} instance can be assigned to at most one
* {@link WebApp} instance.
*
* @author tomyeh
*/
public class Configuration {
private static final Logger log = LoggerFactory.getLogger(Configuration.class);
private static final String PROP_EXPRESS_FACTORY = "org.zkoss.xel.ExpressionFactory.class";
private WebApp _wapp;
/** List of classes. */
private final FastReadArray<Class<?>> _evtInits = new FastReadArray<Class<?>>(Class.class),
_evtCleans = new FastReadArray<Class<?>>(Class.class), _evtSusps = new FastReadArray<Class<?>>(Class.class),
_evtResus = new FastReadArray<Class<?>>(Class.class), _appInits = new FastReadArray<Class<?>>(Class.class),
_appCleans = new FastReadArray<Class<?>>(Class.class),
_sessInits = new FastReadArray<Class<?>>(Class.class),
_sessCleans = new FastReadArray<Class<?>>(Class.class), _dtInits = new FastReadArray<Class<?>>(Class.class),
_dtCleans = new FastReadArray<Class<?>>(Class.class), _execInits = new FastReadArray<Class<?>>(Class.class),
_execCleans = new FastReadArray<Class<?>>(Class.class),
_composers = new FastReadArray<Class<?>>(Class.class),
_initiators = new FastReadArray<Class<?>>(Class.class),
_seoRends = new FastReadArray<Class<?>>(Class.class), _resolvers = new FastReadArray<Class<?>>(Class.class),
_parsers = new FastReadArray<Class<?>>(Class.class);
//since it is called frequently, we use array to avoid synchronization
/** List of objects. */
private final FastReadArray<AggregationListener> _aggregations = new FastReadArray<AggregationListener>(
AggregationListener.class); // since 8.0
private final FastReadArray<URIInterceptor> _uriIntcps = new FastReadArray<URIInterceptor>(URIInterceptor.class);
private final FastReadArray<RequestInterceptor> _reqIntcps = new FastReadArray<RequestInterceptor>(
RequestInterceptor.class);
private final FastReadArray<UiLifeCycle> _uiCycles = new FastReadArray<UiLifeCycle>(UiLifeCycle.class);
private final FastReadArray<PropertiesRenderer> _propRends = new FastReadArray<PropertiesRenderer>(
PropertiesRenderer.class);
private final FastReadArray<String> _labellocs = new FastReadArray<String>(String.class);
private final Map<String, String> _prefs = Collections.synchronizedMap(new HashMap<String, String>());
/** Map(String name, [Class richlet, Map params] or Richilet richlet). */
private final Map<String, Object> _richlets = new HashMap<String, Object>();
/** Map(String path, [String name, boolean wildcard]). */
private final Map<String, Object[]> _richletmaps = new HashMap<String, Object[]>();
/** Map(String deviceType, List(ErrorPage)). */
private final Map<String, List<ErrorPage>> _errpgs = new HashMap<String, List<ErrorPage>>(4);
/** Map(String deviceType+connType, Map(errorCode, uri)) */
private final Map<String, Map<Integer, String>> _errURIs = new HashMap<String, Map<Integer, String>>();
/** Map(String deviceType, TimeoutURIInfo ti) */
private final Map<String, TimeoutURIInfo> _timeoutURIs = Collections
.synchronizedMap(new HashMap<String, TimeoutURIInfo>());
/** Since 8.0.0 Map(String DataHandlerInfo) */
private final Map<String, DataHandlerInfo> _dataHandlers = new HashMap<String, DataHandlerInfo>();
/** Since 8.5.2 Map(String Callback) */
private final Map<String, Callback> _callbacks = new HashMap<String, Callback>();
private Monitor _monitor;
private PerformanceMeter _pfmeter;
private ExecutionMonitor _execmon;
private DesktopRecycle _dtRecycle;
private final FastReadArray<String> _themeURIs = new FastReadArray<String>(String.class);
private ThemeProvider _themeProvider;
private List<ThemeURIHandler> _themeURIHandlers = new LinkedList<ThemeURIHandler>();
/** A set of disabled theme URIs. */
private Set<String> _disThemeURIs;
/** A list of client packages. */
private final FastReadArray<String> _clientpkgs = new FastReadArray<String>(String.class);
private Class<?> _wappcls, _wappftycls, _uiengcls, _dcpcls, _uiftycls, _failmancls, _idgencls, _sesscachecls,
_audeccls, _fileFactory;
private int _dtTimeout = 3600, _sessDktMax = 15, _sessReqMax = 5, _sessPushMax = -1, _sessTimeout = 0,
_sparThdMax = 100, _suspThdMax = -1, _maxUploadSize = 5120, _fileSizeThreshold, _maxProcTime = 3000,
_promptDelay = 900, _tooltipDelay = 800, _autoResendTimeout = 200;
/** since 8.0.2 */
private String _fileRepository;
private String _charsetResp = "UTF-8", _charsetUpload = "UTF-8";
private CharsetFinder _charsetFinderUpload;
/** The event interceptors. */
private final EventInterceptors _eis = new EventInterceptors();
private int _evtTimeWarn = 600; //sec
/** A map of attributes. */
private final Map<String, Object> _attrs = Collections.synchronizedMap(new HashMap<String, Object>());
/** whether to use the event processing thread. */
private boolean _evtThdEnabled; //disabled by default since ZK 5
/** whether zscript is enabled. */
private boolean _zscriptEnabled = true;
/** keep-across-visits. */
private boolean _keepDesktop;
/** Whether to keep the session alive when receiving onTimer.
*/
private boolean _timerKeepAlive;
/** Whether to debug JavaScript. */
private boolean _debugJS;
/** Whether the ZK application is crawlable. */
private boolean _crawlable;
/** Whether to use the same UUID sequence. */
private boolean _repeatUuid;
// ZK-1671: ThemeProvider defined in metainfo/zk/zk.xml from jar file doesn't work
// Depends on the jar file loading order, user-defined theme provider may be
// overridden by StandardThemeProvider
private boolean _customThemeProvider = false;
private boolean _customThemeRegistry = false;
private boolean _customThemeResolver = false;
//F70-ZK-2495: init crash script and timeout
private String _initCrashScript;
private int _initCrashTimeout = -1;
//F80 - store subtree's binder annotation count
private String _binderInitAttribute = null;
private Set<String> _binderAnnotations;
private boolean _sourceMapEnabled = false;
private boolean _historyStateEnabled = true;
private boolean _sendClientErrors = false;
/** Constructor.
*/
public Configuration() {
}
/** Returns the Web application that this configuration belongs to,
* or null if it is not associated yet.
*/
public WebApp getWebApp() {
return _wapp;
}
/** Associates it with a web application.
*/
public void setWebApp(WebApp wapp) {
_wapp = wapp;
}
/** Adds a listener class.
*
* <p>Notice that there is only one listener allowed for the following classes:
* {@link Monitor}, {@link PerformanceMeter}, and {@link DesktopRecycle}.
* On the other hand, any number listeners are allowed for other classes.
*
* <p>Notice that if the listener implements {@link Composer}, it can also
* implement {@link org.zkoss.zk.ui.util.ComposerExt} and/or {@link org.zkoss.zk.ui.util.FullComposer} to have
* more detailed control. However, ComposerExt and FullComposer are meaningless
* to richlets. In additions, an independent
* composer is instantiated for each page so there is synchronization required.
*
* <p>By default, a listener is instantiated when required, and dropped
* after invoked. In other words, a new instance will be instantiated in
* the next invocation. It means you don't have to worry the threading,
* <p>However, for better performance, the following listeners will be instantiated
* in {@link #addListener}, and then used repeatedly. It means it has
* to be thread safe. These listeners include
* {@link URIInterceptor}, {@link RequestInterceptor},
* {@link EventInterceptor}, {@link UiLifeCycle},
* and {@link PropertiesRenderer}.
*
* @param klass the listener class must implement at least one of
* {@link Monitor}, {@link PerformanceMeter}, {@link EventThreadInit},
* {@link EventThreadCleanup}, {@link EventThreadSuspend},
* {@link EventThreadResume}, {@link WebAppInit}, {@link WebAppCleanup},
* {@link SessionInit}, {@link SessionCleanup}, {@link DesktopInit},
* {@link DesktopCleanup}, {@link ExecutionInit}, {@link ExecutionCleanup},
* {@link Composer}, {@link Initiator} (since 5.0.7), {@link SEORenderer} (since 5.0.7),
* {@link PropertiesRenderer} (since 5.0.7),
* {@link VariableResolver},
* {@link URIInterceptor}, {@link RequestInterceptor},
* {@link UiLifeCycle}, {@link DesktopRecycle},
* and/or {@link EventInterceptor} interfaces.
* @see Desktop#addListener
*/
public void addListener(Class<?> klass) throws Exception {
boolean added = false;
Object listener = null;
if (Monitor.class.isAssignableFrom(klass)) {
if (_monitor != null)
throw new UiException(onlyOnce(Monitor.class));
_monitor = (Monitor) (listener = getInstance(klass, listener));
added = true;
}
if (PerformanceMeter.class.isAssignableFrom(klass)) {
if (_pfmeter != null)
throw new UiException(onlyOnce(PerformanceMeter.class));
_pfmeter = (PerformanceMeter) (listener = getInstance(klass, listener));
added = true;
}
if (ExecutionMonitor.class.isAssignableFrom(klass)) {
if (_execmon != null)
throw new UiException(onlyOnce(ExecutionMonitor.class));
_execmon = (ExecutionMonitor) (listener = getInstance(klass, listener));
added = true;
}
if (DesktopRecycle.class.isAssignableFrom(klass)) {
if (_dtRecycle != null)
throw new UiException(onlyOnce(DesktopRecycle.class));
_dtRecycle = (DesktopRecycle) (listener = getInstance(klass, listener));
added = true;
}
if (EventThreadInit.class.isAssignableFrom(klass)) {
_evtInits.add(klass);
added = true;
}
if (EventThreadCleanup.class.isAssignableFrom(klass)) {
_evtCleans.add(klass);
added = true;
}
if (EventThreadSuspend.class.isAssignableFrom(klass)) {
_evtSusps.add(klass);
added = true;
}
if (EventThreadResume.class.isAssignableFrom(klass)) {
_evtResus.add(klass);
added = true;
}
if (WebAppInit.class.isAssignableFrom(klass)) {
_appInits.add(klass);
added = true;
}
if (WebAppCleanup.class.isAssignableFrom(klass)) {
_appCleans.add(klass);
added = true;
}
if (SessionInit.class.isAssignableFrom(klass)) {
_sessInits.add(klass);
added = true;
}
if (SessionCleanup.class.isAssignableFrom(klass)) {
_sessCleans.add(klass);
added = true;
}
if (DesktopInit.class.isAssignableFrom(klass)) {
_dtInits.add(klass);
added = true;
}
if (DesktopCleanup.class.isAssignableFrom(klass)) {
_dtCleans.add(klass);
added = true;
}
if (ExecutionInit.class.isAssignableFrom(klass)) {
_execInits.add(klass);
added = true;
}
if (ExecutionCleanup.class.isAssignableFrom(klass)) {
_execCleans.add(klass);
added = true;
}
if (Composer.class.isAssignableFrom(klass)) {
_composers.add(klass); //not instance
added = true;
}
if (Initiator.class.isAssignableFrom(klass)) {
_initiators.add(klass); //not instance
added = true;
}
if (SEORenderer.class.isAssignableFrom(klass)) {
_seoRends.add(klass);
added = true;
}
if (VariableResolver.class.isAssignableFrom(klass)) {
_resolvers.add(klass); //not instance
added = true;
}
//for better performance, the following listeners are instantiated
//here and shared in the whole application
if (AggregationListener.class.isAssignableFrom(klass)) {
try {
_aggregations.add((AggregationListener) (listener = getInstance(klass, listener)));
} catch (Throwable ex) {
log.error("Failed to instantiate " + klass, ex);
}
added = true;
}
if (URIInterceptor.class.isAssignableFrom(klass)) {
try {
_uriIntcps.add((URIInterceptor) (listener = getInstance(klass, listener)));
} catch (Throwable ex) {
log.error("Failed to instantiate " + klass, ex);
}
added = true;
}
if (RequestInterceptor.class.isAssignableFrom(klass)) {
try {
_reqIntcps.add((RequestInterceptor) (listener = getInstance(klass, listener)));
} catch (Throwable ex) {
log.error("Failed to instantiate " + klass, ex);
}
added = true;
}
if (EventInterceptor.class.isAssignableFrom(klass)) {
try {
_eis.addEventInterceptor((EventInterceptor) (listener = getInstance(klass, listener)));
} catch (Throwable ex) {
log.error("Failed to instantiate " + klass, ex);
}
added = true;
}
if (UiLifeCycle.class.isAssignableFrom(klass)) {
try {
_uiCycles.add((UiLifeCycle) (listener = getInstance(klass, listener)));
} catch (Throwable ex) {
log.error("Failed to instantiate " + klass, ex);
}
added = true;
}
if (PropertiesRenderer.class.isAssignableFrom(klass)) {
try {
_propRends.add((PropertiesRenderer) (listener = getInstance(klass, listener)));
} catch (Throwable ex) {
log.error("Failed to instantiate " + klass, ex);
}
added = true;
}
if (NamespaceParser.class.isAssignableFrom(klass)) {
_parsers.add(klass);
added = true;
}
if (!added) {
for (AggregationListener al : (List<AggregationListener>) Arrays.asList(_aggregations.toArray())) {
if (al.isHandled(klass)) {
added = true;
break;
}
}
}
if (!added)
throw new UiException("Unknown listener: " + klass);
}
private static final String onlyOnce(Class<?> cls) {
return cls + " can be assigned only once";
}
private static Object getInstance(Class<?> klass, Object listener) throws Exception {
return listener != null ? listener : klass.newInstance();
}
/** Removes a listener class.
* @see Desktop#removeListener
*/
public void removeListener(final Class<?> klass) {
if (_monitor != null && _monitor.getClass().equals(klass))
_monitor = null;
if (_pfmeter != null && _pfmeter.getClass().equals(klass))
_pfmeter = null;
if (_execmon != null && _execmon.getClass().equals(klass))
_execmon = null;
if (_dtRecycle != null && _dtRecycle.getClass().equals(klass))
_dtRecycle = null;
_evtInits.remove(klass);
_evtCleans.remove(klass);
_evtSusps.remove(klass);
_evtResus.remove(klass);
_appInits.remove(klass);
_appCleans.remove(klass);
_sessInits.remove(klass);
_sessCleans.remove(klass);
_dtInits.remove(klass);
_dtCleans.remove(klass);
_execInits.remove(klass);
_execCleans.remove(klass);
_composers.remove(klass);
_initiators.remove(klass);
_seoRends.remove(klass);
_resolvers.remove(klass);
_parsers.remove(klass);
final SameClass sc = new SameClass(klass);
_aggregations.removeBy(sc, true);
_uriIntcps.removeBy(sc, true);
_reqIntcps.removeBy(sc, true);
_uiCycles.removeBy(sc, true);
_propRends.removeBy(sc, true);
_eis.removeEventInterceptor(klass);
}
/** Constructs a list of {@link EventThreadInit} instances and invokes
* {@link EventThreadInit#prepare} for
* each relevant listener registered by {@link #addListener}.
*
* <p>Used only internally (by {@link UiEngine} before starting an event
* processing thread).
*
* @exception UiException to prevent a thread from being processed
* if {@link EventThreadInit#prepare} throws an exception
* @return a list of {@link EventThreadInit} instances that are
* constructed in this method (and their {@link EventThreadInit#prepare}
* are called successfully), or null.
*/
public List<EventThreadInit> newEventThreadInits(Component comp, Event evt) throws UiException {
final Class<?>[] ary = _evtInits.toArray();
if (ary.length == 0)
return null;
final List<EventThreadInit> inits = new LinkedList<EventThreadInit>();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
final EventThreadInit init = (EventThreadInit) klass.newInstance();
init.prepare(comp, evt);
inits.add(init);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the event being processed
}
}
return inits;
}
/** Invokes {@link EventThreadInit#init} for each instance returned
* by {@link #newEventThreadInits}.
*
* <p>Used only internally.
*
* @param inits a list of {@link EventThreadInit} instances returned from
* {@link #newEventThreadInits}, or null if no instance at all.
* @param comp the component which the event is targeting
* @param evt the event to process
* @exception UiException to prevent a thread from being processed
* if {@link EventThreadInit#init} throws an exception
* @return false if you want to ignore the event, i.e., not to proceed
* any event processing for the specified event (evt).
*/
public boolean invokeEventThreadInits(List<EventThreadInit> inits, Component comp, Event evt) throws UiException {
if (inits == null || inits.isEmpty())
return true; //not to ignore
for (EventThreadInit fn : inits) {
try {
if (!fn.init(comp, evt))
return false; //ignore the event
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the event being processed
}
}
return true;
}
/** Invokes {@link EventThreadCleanup#cleanup} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link EventThreadCleanup} is constructed first,
* and then invoke {@link EventThreadCleanup#cleanup}.
*
* <p>It never throws an exception but logs and adds it to the errs argument,
* if not null.
*
* @param comp the component which the event is targeting
* @param evt the event to process
* @param errs a list of exceptions (java.lang.Throwable) if any exception
* occurred before this method is called, or null if no exception at all.
* Note: you can manipulate the list directly to add or clean up exceptions.
* For example, if exceptions are fixed correctly, you can call errs.clear()
* such that no error message will be displayed at the client.
* @param silent whether not to log the exception
* @return a list of {@link EventThreadCleanup}, or null
* @since 3.6.3
*/
public List<EventThreadCleanup> newEventThreadCleanups(Component comp, Event evt, List<Throwable> errs,
boolean silent) {
final Class<?>[] ary = _evtCleans.toArray();
if (ary.length == 0)
return null;
final List<EventThreadCleanup> cleanups = new LinkedList<EventThreadCleanup>();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
final EventThreadCleanup cleanup = (EventThreadCleanup) klass.newInstance();
cleanup.cleanup(comp, evt, errs);
cleanups.add(cleanup);
} catch (Throwable t) {
if (errs != null)
errs.add(t);
if (!silent)
log.error("Failed to invoke " + klass, t);
}
}
return cleanups.isEmpty() ? null : cleanups;
}
/** Invoke {@link EventThreadCleanup#complete} for each instance returned by
* {@link #newEventThreadCleanups}.
*
* <p>Used only internally.
*
* <p>It never throws an exception but logs and adds it to the errs argument,
* if not null.
*
* @param cleanups a list of {@link EventThreadCleanup} instances returned from
* {@link #newEventThreadCleanups}, or null if no instance at all.
* @param errs used to hold the exceptions that are thrown by
* {@link EventThreadCleanup#complete}.
* If null, all exceptions are ignored (but logged).
* @param silent whether not to log the exception
* @since 3.6.3
*/
public void invokeEventThreadCompletes(List<EventThreadCleanup> cleanups, Component comp, Event evt,
List<Throwable> errs, boolean silent) {
if (cleanups == null || cleanups.isEmpty())
return;
for (EventThreadCleanup fn : cleanups) {
try {
fn.complete(comp, evt);
} catch (Throwable ex) {
if (errs != null)
errs.add(ex);
if (!silent)
log.error("Failed to invoke " + fn, ex);
}
}
}
/** Constructs a list of {@link EventThreadSuspend} instances and invokes
* {@link EventThreadSuspend#beforeSuspend} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>Note: caller shall execute in the event processing thread.
*
* @param comp the component which the event is targeting
* @param evt the event to process
* @param obj which object that {@link Executions#wait}
* is called with.
* @exception UiException to prevent a thread from suspending
* @return a list of {@link EventThreadSuspend}, or null
*/
public List<EventThreadSuspend> newEventThreadSuspends(Component comp, Event evt, Object obj) {
final Class<?>[] ary = _evtSusps.toArray();
if (ary.length == 0)
return null;
final List<EventThreadSuspend> suspends = new LinkedList<EventThreadSuspend>();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
final EventThreadSuspend suspend = (EventThreadSuspend) klass.newInstance();
suspend.beforeSuspend(comp, evt, obj);
suspends.add(suspend);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the event being suspended
}
}
return suspends;
}
/** Invokes {@link EventThreadSuspend#afterSuspend} for each relevant
* listener registered by {@link #addListener}.
* Unlike {@link #invokeEventThreadSuspends}, caller shall execute in
* the main thread (a.k.a., servlet thread).
*
* <p>Used only internally.
*
* <p>Unlike {@link #invokeEventThreadSuspends}, exceptions are logged
* and ignored.
*
* @param suspends a list of {@link EventThreadSuspend} instances returned
* from {@link #newEventThreadSuspends}, or null if no instance at all.
* @param comp the component which the event is targeting
* @param evt the event to process
*/
public void invokeEventThreadSuspends(List<EventThreadSuspend> suspends, Component comp, Event evt)
throws UiException {
if (suspends == null || suspends.isEmpty())
return;
for (EventThreadSuspend fn : suspends) {
try {
fn.afterSuspend(comp, evt);
} catch (Throwable ex) {
log.error("Failed to invoke " + fn + " after suspended", ex);
}
}
}
/** Constructs a list of {@link EventThreadResume} instances and invokes
* {@link EventThreadResume#beforeResume} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally (by {@link UiEngine} when resuming a suspended event
* thread).
* Notice: it executes in the main thread (i.e., the servlet thread).
*
* @param comp the component which the event is targeting
* @param evt the event to process
* @exception UiException to prevent a thread from being resumed
* if {@link EventThreadResume#beforeResume} throws an exception
* @return a list of {@link EventThreadResume} instances that are constructed
* in this method (and their {@link EventThreadResume#beforeResume}
* are called successfully), or null.
*/
public List<EventThreadResume> newEventThreadResumes(Component comp, Event evt) throws UiException {
final Class<?>[] ary = _evtResus.toArray();
if (ary.length == 0)
return null;
final List<EventThreadResume> resumes = new LinkedList<EventThreadResume>();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
final EventThreadResume resume = (EventThreadResume) klass.newInstance();
resume.beforeResume(comp, evt);
resumes.add(resume);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the event being resumed
}
}
return resumes;
}
/** Invokes {@link EventThreadResume#afterResume} for each instance returned
* by {@link #newEventThreadResumes}.
*
* <p>Used only internally.
*
* <p>It never throws an exception but logs and adds it to the errs argument,
* if not null.
*
* @param resumes a list of {@link EventThreadResume} instances returned from
* {@link #newEventThreadResumes}, or null if no instance at all.
* @param comp the component which the event is targeting
* @param evt the event to process
* If null, all exceptions are ignored (but logged)
*/
public void invokeEventThreadResumes(List<EventThreadResume> resumes, Component comp, Event evt)
throws UiException {
if (resumes == null || resumes.isEmpty())
return;
for (EventThreadResume fn : resumes) {
try {
fn.afterResume(comp, evt);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
}
}
}
/** Invokes {@link EventThreadResume#abortResume} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link EventThreadResume} is constructed first,
* and then invoke {@link EventThreadResume#abortResume}.
*
* <p>It never throws an exception but logging.
*
* @param comp the component which the event is targeting
* @param evt the event to process
*/
public void invokeEventThreadResumeAborts(Component comp, Event evt) {
final Class<?>[] ary = _evtResus.toArray();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
((EventThreadResume) klass.newInstance()).abortResume(comp, evt);
} catch (Throwable ex) {
log.error("Failed to invoke " + klass + " for aborting", ex);
}
}
}
/** Invokes {@link WebAppInit#init} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link WebAppInit} is constructed first,
* and then invoke {@link WebAppInit#init}.
*
* @exception UiException to prevent a webapp from being created
*/
public void invokeWebAppInits() throws UiException {
final Class<?>[] ary = _appInits.toArray();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
((WebAppInit) klass.newInstance()).init(_wapp);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the creation of a webapp
}
}
}
/** Invokes {@link WebAppCleanup#cleanup} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link WebAppCleanup} is constructed first,
* and then invoke {@link WebAppCleanup#cleanup}.
*
* <p>It never throws an exception.
*/
public void invokeWebAppCleanups() {
final Class<?>[] ary = _appCleans.toArray();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
((WebAppCleanup) klass.newInstance()).cleanup(_wapp);
} catch (NoClassDefFoundError ex) { //Bug 3046360
} catch (Throwable ex) {
log.error("Ignored: failed to invoke " + klass, ex);
}
}
}
/** Invokes {@link SessionInit#init} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link SessionInit} is constructed first,
* and then invoke {@link SessionInit#init}.
*
* @param sess the session that is created
* @param request the original request. If HTTP, it is
* javax.servlet.http.HttlServletRequest.
* @exception UiException to prevent a session from being created
* @since 3.0.1
*/
public void invokeSessionInits(Session sess, Object request) throws UiException {
final Class<?>[] ary = _sessInits.toArray();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
final SessionInit fn = (SessionInit) klass.newInstance();
fn.init(sess, request);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the creation of a session
}
}
}
/** Invokes {@link SessionCleanup#cleanup} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link SessionCleanup} is constructed first,
* and then invoke {@link SessionCleanup#cleanup}.
*
* <p>It never throws an exception.
*
* @param sess the session that is being destroyed
*/
public void invokeSessionCleanups(Session sess) {
final Class<?>[] ary = _sessCleans.toArray();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
((SessionCleanup) klass.newInstance()).cleanup(sess);
} catch (Throwable ex) {
log.error("Failed to invoke " + klass, ex);
}
}
}
/** Invokes {@link DesktopInit#init} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link DesktopInit} is constructed first,
* and then invoke {@link DesktopInit#init}.
*
* @param desktop the desktop that is created
* @param request the original request. If HTTP, it is
* javax.servlet.http.HttlServletRequest.
* @exception UiException to prevent a desktop from being created
* @since 3.0.1
*/
public void invokeDesktopInits(Desktop desktop, Object request) throws UiException {
final Class<?>[] ary = _dtInits.toArray();
for (int j = 0; j < ary.length; ++j) {
final Class<?> klass = ary[j];
try {
final DesktopInit fn = (DesktopInit) klass.newInstance();
fn.init(desktop, request);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the creation of a session
}
}
}
/** Invokes {@link DesktopCleanup#cleanup} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link DesktopCleanup} is constructed first,
* and then invoke {@link DesktopCleanup#cleanup}.
*
* <p>It never throws an exception.
*
* @param desktop the desktop that is being destroyed
*/
public void invokeDesktopCleanups(Desktop desktop) {
final Class<?>[] ary = _dtCleans.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
((DesktopCleanup) ary[j].newInstance()).cleanup(desktop);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes {@link ExecutionInit#init} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link ExecutionInit} is constructed first,
* and then invoke {@link ExecutionInit#init}.
*
* @param exec the execution that is created
* @param parent the previous execution, or null if no previous at all
* @exception UiException to prevent an execution from being created
*/
public void invokeExecutionInits(Execution exec, Execution parent) throws UiException {
final Class<?>[] ary = _execInits.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
((ExecutionInit) ary[j].newInstance()).init(exec, parent);
} catch (Throwable ex) {
throw UiException.Aide.wrap(ex);
//Don't intercept; to prevent the creation of a session
}
}
}
/** Invokes {@link ExecutionCleanup#cleanup} for each relevant
* listener registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>An instance of {@link ExecutionCleanup} is constructed first,
* and then invoke {@link ExecutionCleanup#cleanup}.
*
* <p>It never throws an exception but logs and adds it to the errs argument,
* if not null.
*
* @param exec the execution that is being destroyed
* @param parent the previous execution, or null if no previous at all
* @param errs a list of exceptions (java.lang.Throwable) if any exception
* occurred before this method is called, or null if no exception at all.
* Note: you can manipulate the list directly to add or clean up exceptions.
* For example, if exceptions are fixed correctly, you can call errs.clear()
* such that no error message will be displayed at the client.
*/
public void invokeExecutionCleanups(Execution exec, Execution parent, List<Throwable> errs) {
final Class<?>[] ary = _execCleans.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
((ExecutionCleanup) ary[j].newInstance()).cleanup(exec, parent, errs);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
if (errs != null)
errs.add(ex);
}
}
}
/** Invokes {@link URIInterceptor#request} for each relevant listener
* registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>If any of them throws an exception, the exception is propagated to
* the caller.
*
* @exception UiException if it is rejected by the interceptor.
* Use {@link UiException#getCause} to retrieve the cause.
*/
public void invokeURIInterceptors(String uri) {
URIInterceptor[] ary = _uriIntcps.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].request(uri);
} catch (Exception ex) {
throw UiException.Aide.wrap(ex);
}
}
}
/** Invokes {@link RequestInterceptor#request} for each relevant listener
* registered by {@link #addListener}.
*
* <p>Used only internally.
*
* <p>If any of them throws an exception, the exception is propagated to
* the caller.
*
* @exception UiException if it is rejected by the interceptor.
* Use {@link UiException#getCause} to retrieve the cause.
*/
public void invokeRequestInterceptors(Session sess, Object request, Object response) {
RequestInterceptor[] ary = _reqIntcps.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].request(sess, request, response);
} catch (Exception ex) {
throw UiException.Aide.wrap(ex);
}
}
}
/** Adds the location of a properties file for i18n labels.
* <p>Default: none (/WEB-INF/zk-label.properties is assumed).
* <p>Notice that this method has no effect after the web server has been
* started. Thus, it is suggested to use the label-location element in zk.xml instead.
* @since 5.0.7
*/
public void addLabelLocation(String location) {
if (location == null || location.length() == 0)
throw new IllegalArgumentException();
_labellocs.add(location);
}
/** Returns an array of the locations of properties files registered
* by {@link #addLabelLocation}.
* @since 5.0.7
*/
public String[] getLabelLocations() {
return _labellocs.toArray();
}
/** Returns the system-level composer or null if none is registered.
* To register a system-level composer, use {@link #addListener}.
* <p>Notice that any number of composers can be registered,
* and a single composer is returned to represent them all.
* @since 5.0.1
*/
public Composer<?> getComposer(Page page) throws Exception {
return MultiComposer.getComposer(page, _composers.toArray());
}
/** Returns a readonly list of the system-level initiators.
* It is empty if none is registered.
* To register a system-level initiator, use {@link #addListener}.
* @since 5.0.7
*/
public Initiator[] getInitiators() {
final Class<?>[] initclses = _initiators.toArray();
if (initclses.length == 0)
return new Initiator[0];
final List<Initiator> inits = new LinkedList<Initiator>();
for (int j = 0; j < initclses.length; ++j) {
try {
inits.add((Initiator) initclses[j].newInstance());
} catch (Throwable ex) {
log.error("Failed to instantiate " + initclses[j]);
}
}
return inits.toArray(new Initiator[inits.size()]);
}
/** Returns a readonly list of the system-level NamespaceParsers.
* It is empty if none is registered.
* To register a system-level NamespaceParsers, use {@link #addListener}.
* @since 7.0.3
*/
@SuppressWarnings("unchecked")
public List<NamespaceParser> getNamespaceParsers() {
final Class<?>[] initclses = _parsers.toArray();
if (initclses.length == 0)
return Collections.EMPTY_LIST;
final List<NamespaceParser> inits = new LinkedList<NamespaceParser>();
for (int j = 0; j < initclses.length; ++j) {
try {
inits.add((NamespaceParser) initclses[j].newInstance());
} catch (Throwable ex) {
log.error("Failed to instantiate " + initclses[j]);
}
}
return inits;
}
/** Returns a readonly list of the system-level SEO renderer.
* It is empty if none is registered.
* To register a system-level SEO renderers, use {@link #addListener}.
* <p>Notice that, once registered, an instance is instantiated before
* invoking {@link SEORenderer#render}.
* @since 5.0.7
*/
public SEORenderer[] getSEORenderers() {
final Class<?>[] sdclses = _seoRends.toArray();
if (sdclses.length == 0)
return new SEORenderer[0];
final List<SEORenderer> sds = new LinkedList<SEORenderer>();
for (int j = 0; j < sdclses.length; ++j) {
try {
sds.add((SEORenderer) sdclses[j].newInstance());
} catch (Throwable ex) {
log.error("Failed to instantiate " + sdclses[j]);
}
}
return sds.toArray(new SEORenderer[sds.size()]);
}
/** Initializes the given page with the variable resolvers registered
* by {@link #addListener}.
* It must be called before accessing a page (actually in {@link org.zkoss.zk.ui.sys.PageCtrl#preInit}).
* @since 5.0.4
*/
public void init(Page page) {
final Class<?>[] classes = _resolvers.toArray();
for (int j = 0; j < classes.length; ++j) {
try {
page.addVariableResolver((VariableResolver) classes[j].newInstance());
} catch (Throwable ex) {
log.error("Failed to instantiate " + classes[j], ex);
}
}
}
/** Returns a readonly list of the system-level properties renders.
* It is empty if none is registered.
* To register a system-level properties renders, use {@link #addListener}.
* <p>Notice that, once registered, it is instantiated immediately,
* and the same instance is shared for rendering the properties of every component.
* @since 5.0.7
*/
public PropertiesRenderer[] getPropertiesRenderers() {
return _propRends.toArray();
}
/** Invokes {@link UiLifeCycle#afterComponentAttached}
* when a component is attached to a page.
* @since 3.0.6
*/
public void afterComponentAttached(Component comp, Page page) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterComponentAttached(comp, page);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes {@link UiLifeCycle#afterComponentDetached}
* when a component is detached from a page.
* @since 3.0.6
*/
public void afterComponentDetached(Component comp, Page prevpage) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterComponentDetached(comp, prevpage);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes a callback function {@link Callback#call(Object data)}
* @param name the name of the callback function.
* @param data the parameter for the callback function.
* @since 8.5.2
*/
public void invokeCallback(String name, Object data) {
Callback callback = _callbacks.get(name);
if (callback != null)
callback.call(data);
}
/** Register a callback function {@link Callback#call(Object data)}
* @param name the name of the callback function.
* @param callback the callback function to register into Configuration class.
* @since 8.5.2
*/
public void registerCallBack(String name, Callback callback) {
_callbacks.put(name, callback);
}
/** Unregister a callback function {@link Callback#call(Object data)}
* @param name the name of the callback function.
* @since 8.5.2
*/
public void unregisterCallBack(String name) {
_callbacks.remove(name);
}
/** Returns if a callback function {@link Callback#call(Object data)} is registered.
* @param name the name of the callback function.
* @return If a callback function is registered.
* @since 8.5.2
*/
public boolean hasCallBack(String name) {
return _callbacks.containsKey(name);
}
/** Invokes {@link UiLifeCycle#afterShadowAttached(ShadowElement, Component)}
* when a shadow is attached to a host.
* @since 8.0.0
*/
public void afterShadowAttached(ShadowElement shadow, Component host) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterShadowAttached(shadow, host);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes {@link UiLifeCycle#afterShadowDetached(ShadowElement, Component)}
* when a shadow is detached from a host.
* @since 8.0.0
*/
public void afterShadowDetached(ShadowElement shadow, Component prevhost) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterShadowDetached(shadow, prevhost);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes {@link UiLifeCycle#afterComponentMoved}
* when a component is moved (a.k.a., page changed).
* @since 3.0.6
*/
public void afterComponentMoved(Component parent, Component child, Component prevparent) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterComponentMoved(parent, child, prevparent);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes {@link UiLifeCycle#afterPageAttached}
* when a compnent's parent is changed.
* @since 3.0.6
*/
public void afterPageAttached(Page page, Desktop desktop) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterPageAttached(page, desktop);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Invokes {@link UiLifeCycle#afterPageDetached}
* when a compnent's parent is changed.
* @since 3.0.6
*/
public void afterPageDetached(Page page, Desktop prevdesktop) {
final UiLifeCycle[] ary = _uiCycles.toArray();
for (int j = 0; j < ary.length; ++j) {
try {
ary[j].afterPageDetached(page, prevdesktop);
} catch (Throwable ex) {
log.error("Failed to invoke " + ary[j], ex);
}
}
}
/** Adds an CSS resource that will be generated for each ZUML desktop.
*
* <p>Note: if {@link ThemeProvider} is specified ({@link #setThemeProvider}),
* the final theme URIs generated depend on {@link ThemeProvider#getThemeURIs}.
*/
public void addThemeURI(String uri) {
if (uri == null || uri.length() == 0)
throw new IllegalArgumentException("empty");
_themeURIs.add(uri);
}
/** Returns a readonly list of the URI of the CSS resources that will be
* generated for each ZUML desktop (never null).
*
* <p>Default: an array with zero length.
*/
public String[] getThemeURIs() {
return _themeURIs.toArray();
}
/** Specifies what theme URI to be disabled.
*
* <p>Note: if {@link ThemeProvider} is used ({@link #setThemeProvider}),
* the URIs of the theme depend on {@link ThemeProvider#getThemeURIs}.
*
* @param uri the theme URI to disable
* @since 3.0.0
*/
public void addDisabledThemeURI(String uri) {
if (uri == null || uri.length() == 0)
throw new IllegalArgumentException();
synchronized (this) {
if (_disThemeURIs == null)
_disThemeURIs = Collections.synchronizedSet(new HashSet<String>(4));
}
_disThemeURIs.add(uri);
}
/** Returns a set of the theme URIs that are disabled (never null).
*
* @since 3.0.0
* @see #addDisabledThemeURI
*/
public Set<String> getDisabledThemeURIs() {
if (_disThemeURIs != null)
return _disThemeURIs;
return Collections.emptySet();
}
/**
* Add a {@link ThemeURIHandler}.
*
* <p>Note: if {@link ThemeProvider} is specified ({@link #setThemeProvider}),
* the final generated theme URIs depend on {@link ThemeProvider#getThemeURIs}.
*
* @since 9.6.0
*/
public void addThemeURIHandler(ThemeURIHandler themeURIHandler) {
if (themeURIHandler == null)
throw new IllegalArgumentException("empty");
_themeURIHandlers.add(themeURIHandler);
}
/**
* Returns a list of {@link ThemeURIHandler}.
*
* @since 9.6.0
*/
public List<ThemeURIHandler> getThemeURIHandlers() {
return _themeURIHandlers;
}
/** Returns the theme provider for the current execution,
* or null if not available.
*
* <p>Default: null.
*
* <p>Note: if specified, the final theme URIs is decided by
* the provider. The URIs specified in {@link #getThemeURIs} are
* passed to provider, and it has no effect if the provider decides
* to ignore them.
* @since 3.0.0
* @see #getThemeURIs
* @see #getDisabledThemeURIs
*/
public ThemeProvider getThemeProvider() {
return _themeProvider;
}
/** Sets the theme provider for the current execution,
* or null if not available.
*
* @param provider the theme provide. If null, the default theme URIs
* will be used.
* @see #getThemeProvider
* @since 3.0.0
*/
public void setThemeProvider(ThemeProvider provider) {
_themeProvider = provider;
}
/** Sets the class used to handle UI loading and updates, or null to
* use the default.
* It must implement {@link UiEngine}.
*/
public void setUiEngineClass(Class<?> cls) {
if (cls != null && !UiEngine.class.isAssignableFrom(cls))
throw new IllegalArgumentException("UiEngine not implemented: " + cls);
_uiengcls = cls;
}
/** Returns the class used to handle UI loading and updates,
* or null if default is used.
* It must implement {@link UiEngine}.
*/
public Class<?> getUiEngineClass() {
return _uiengcls;
}
/** Sets the class used to represent this Web application,
* or null to use the default.
* It must implement {@link WebApp} and {@link WebAppCtrl}
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
*/
public void setWebAppClass(Class<?> cls) {
if (cls != null && (!WebApp.class.isAssignableFrom(cls) || !WebAppCtrl.class.isAssignableFrom(cls)))
throw new IllegalArgumentException("WebApp or WebAppCtrl not implemented: " + cls);
_wappcls = cls;
}
/** Returns the class used to represent this Web application,
* or null if default is used.
* It must implement {@link WebApp} and {@link WebAppCtrl}
*/
public Class<?> getWebAppClass() {
return _wappcls;
}
/** Sets the class used to instantiate an instance representing this Web application,
* or null to use the default.
*
* <p>Note: {@link #setWebAppClass} has the higher priority if not null.
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
* @param cls the class that implements {@link WebAppFactory}.
* @since 6.0.0
*/
public void setWebAppFactoryClass(Class<?> cls) {
if (cls != null && !WebAppFactory.class.isAssignableFrom(cls))
throw new IllegalArgumentException("WebAppFactory not implemented: " + cls);
_wappftycls = cls;
}
/** Returns the class used to instantiate an instance representing this Web application,
* or null if default is used.
* It must implement {@link WebAppFactory}.
* <p>Note: {@link #getWebAppClass} has the higher priority if not null.
* @since 6.0.0
*/
public Class<?> getWebAppFactoryClass() {
return _wappftycls;
}
/** Sets the class used to provide the desktop cache, or null to
* use the default.
* It must implement {@link DesktopCacheProvider}.
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
*/
public void setDesktopCacheProviderClass(Class<?> cls) {
if (cls != null && !DesktopCacheProvider.class.isAssignableFrom(cls))
throw new IllegalArgumentException("DesktopCacheProvider not implemented: " + cls);
_dcpcls = cls;
}
/** Returns the class used to provide the desktop cache, or null
* if default is used.
* It must implement {@link DesktopCacheProvider}.
*/
public Class<?> getDesktopCacheProviderClass() {
return _dcpcls;
}
/** Sets the class used to instantiate desktops, pages and components, or
* null to use the default.
* It must implement {@link UiFactory},
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
*/
public void setUiFactoryClass(Class<?> cls) {
if (cls != null && !UiFactory.class.isAssignableFrom(cls))
throw new IllegalArgumentException("UiFactory not implemented: " + cls);
_uiftycls = cls;
}
/** Returns the class used to instantiate desktops, pages and components,
* or null if default is used.
* It must implement {@link UiFactory},
*/
public Class<?> getUiFactoryClass() {
return _uiftycls;
}
/** Sets the class used to handle the failover mechanism, or null if
* no custom failover mechanism.
* It must implement {@link FailoverManager}.
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
*/
public void setFailoverManagerClass(Class<?> cls) {
if (cls != null && !FailoverManager.class.isAssignableFrom(cls))
throw new IllegalArgumentException("FailoverManager not implemented: " + cls);
_failmancls = cls;
}
/** Returns the class used to handle the failover mechanism,
* or null if no custom failover mechanism.
* It must implement {@link FailoverManager}.
*/
public Class<?> getFailoverManagerClass() {
return _failmancls;
}
/** Sets the class that is used to generate UUID/ID of desktop,
* page and components, or null to use the default.
* It must implement {@link IdGenerator}.
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
* @since 2.4.1
*/
public void setIdGeneratorClass(Class<?> cls) {
if (cls != null && !IdGenerator.class.isAssignableFrom(cls))
throw new IllegalArgumentException("IdGenerator not implemented: " + cls);
_idgencls = cls;
}
/** Returns the class used to generate UUID/ID for desktop,
* page and components, or null if the default shall be used.
* It must implement {@link IdGenerator}
* @since 2.4.1
*/
public Class<?> getIdGeneratorClass() {
return _idgencls;
}
/** Sets the class that is used to store ZK sessions,
* or null to use the default.
* It must implement {@link SessionCache}.
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
* @since 3.0.5
*/
public void setSessionCacheClass(Class<?> cls) {
if (cls != null && !SessionCache.class.isAssignableFrom(cls))
throw new IllegalArgumentException("SessionCache not implemented: " + cls);
_sesscachecls = cls;
}
/** Returns the class used to store ZK sessions, or null
* if the default shall be used.
* It must implement {@link SessionCache}.
* @since 3.0.5
*/
public Class<?> getSessionCacheClass() {
return _sesscachecls;
}
/** Sets the class that is used to create a file item for fileupload,
* or null to use the default.
* It must implement {@link DiskFileItemFactory}.
*
* @since 8.0.2
*/
public void setFileItemFactoryClass(Class<?> cls) {
if (cls != null && !DiskFileItemFactory.class.isAssignableFrom(cls))
throw new IllegalArgumentException("DiskFileItemFactory not implemented: " + cls);
_fileFactory = cls;
}
/** Returns the class used to create a file item for fileupload, or null
* if the default shall be used.
* It must implement {@link DiskFileItemFactory}.
* @since 8.0.2
*/
public Class<?> getFileItemFactoryClass() {
return _fileFactory;
}
/** Sets the class that is used to decode AU requests,
* or null to use the default.
* It must implement {@link AuDecoder}.
*
* <p>Note: you have to set the class before {@link WebApp} is created.
* Otherwise, it won't have any effect.
* @since 5.0.4
*/
public void setAuDecoderClass(Class<?> cls) {
if (cls != null && !AuDecoder.class.isAssignableFrom(cls))
throw new IllegalArgumentException("AuDecoder not implemented: " + cls);
_audeccls = cls;
}
/** Returns the class used to decode AU requests, or null
* if the default shall be used.
* It must implement {@link AuDecoder}.
* @since 5.0.4
*/
public Class<?> getAuDecoderClass() {
return _audeccls;
}
/** Specifies the maximal allowed time to process events, in milliseconds.
* ZK will keep processing the requests until all requests are processed,
* or the maximal allowed time expires.
*
* <p>Default: 3000.
*
* <p>Note: since 3.0.0, this setting has no effect on AU requests.
* It controls only the requests from the client-polling server push.
*
* @param time the maximal allowed time to process events.
* It must be positive.
*/
public void setMaxProcessTime(int time) {
_maxProcTime = time;
}
/** Returns the maximal allowed time to process events, in milliseconds.
* It is always positive.
*/
public int getMaxProcessTime() {
return _maxProcTime;
}
/** Specifies the maximal allowed upload size, in kilobytes.
*
* <p>Default: 5120.
*
* @param sz the maximal allowed upload size.
* A negative value indicates there is no limit.
*/
public void setMaxUploadSize(int sz) {
_maxUploadSize = sz;
}
/** Returns the maximal allowed upload size, in kilobytes, or
* a negative value if no limit.
*/
public int getMaxUploadSize() {
return _maxUploadSize;
}
/** Specifies the threshold at which a temporary file is created as a
* buffer, in kilobytes.
*
* <p>Default: 128.
*
* @param sz the file size threshold
* A negative value implies default setting.
* @since 5.0.8
*/
public void setFileSizeThreshold(int sz) {
_fileSizeThreshold = sz;
}
/** Returns the threshold at which a temporary file is created as a
* buffer, in kilobytes, or a negative value which implies default setting.
* @since 5.0.8
*/
public int getFileSizeThreshold() {
return _fileSizeThreshold;
}
/**
* Sets the directory in which uploaded files will be stored, if stored on disk.
* @param directory
* @since 8.0.2
*/
public void setFileRepository(String directory) {
_fileRepository = directory;
}
/**
* Returns the directory in which uploaded files will be stored, if stored on disk.
* <p>Default: null</p>
* @since 8.0.2
*/
public String getFileRepository() {
return _fileRepository;
}
/** Returns the charset used to encode the uploaded text file
* (never null).
*
* <p>Default: UTF-8.
* @see #getUploadCharsetFinder
*/
public String getUploadCharset() {
return _charsetUpload;
}
/** Sets the charset used to encode the upload text file.
*
* <p>Note: {@link #setUploadCharsetFinder} has the higher priority.
*
* @param charset the charset to use.
* If null or empty, UTF-8 is assumed.
* @see #setUploadCharsetFinder
*/
public void setUploadCharset(String charset) {
_charsetUpload = charset != null && charset.length() > 0 ? charset : "UTF-8";
}
/** Returns the finder that is used to decide the character set
* for the uploaded text file(s), or null if not available.
*
* <p>Default: null
* @since 3.0.0
* @see #getUploadCharset
*/
public CharsetFinder getUploadCharsetFinder() {
return _charsetFinderUpload;
}
/** Sets the finder that is used to decide the character set
* for the uploaded text file(s), or null if not available.
*
* <p>It has the higher priority than {@link #setUploadCharset}.
* In other words, {@link #getUploadCharset} is used only if
* this method returns null or {@link CharsetFinder#getCharset}
* returns null.
*
* @since 3.0.0
* @see #setUploadCharset
*/
public void setUploadCharsetFinder(CharsetFinder finder) {
_charsetFinderUpload = finder;
}
/** Specifies the time, in seconds, between client requests
* before ZK will invalidate the desktop.
*
* <p>Default: 3600 (1 hour).
*
* <p>A negative value indicates the desktop should never timeout.
*/
public void setDesktopMaxInactiveInterval(int secs) {
_dtTimeout = secs;
}
/** Returns the time, in seconds, between client requests
* before ZK will invalidate the desktop.
*
* <p>Notice that this timeout is used only if JVM starts
* GC when the memory is running low.
*
* <p>A negative value indicates the desktop should never timeout.
*/
public int getDesktopMaxInactiveInterval() {
return _dtTimeout;
}
/** Specifies the time, in milliseconds, before ZK Client Engine shows
* a dialog to prompt users that the request is in processing.
*
* <p>Default: 900
*/
public void setProcessingPromptDelay(int minisecs) {
_promptDelay = minisecs;
}
/** Returns the time, in milliseconds, before ZK Client Engine shows
* a dialog to prompt users that the request is in processing.
*/
public int getProcessingPromptDelay() {
return _promptDelay;
}
/** Specifies the time, in milliseconds, before ZK Client Engine shows
* the tooltip when a user moves the mouse over particular UI components.
*
* <p>Default: 800
*/
public void setTooltipDelay(int minisecs) {
_tooltipDelay = minisecs;
}
/** Returns the time, in milliseconds, before ZK Client Engine shows
* the tooltip when a user moves the mouse over particular UI components.
*/
public int getTooltipDelay() {
return _tooltipDelay;
}
/** Specifies the timeout, in milliseconds, to re-send the AU request when
* the server's service unavailable or timeout.
* <p>Default: 200
* @since 6.5.2
*/
public void setAutoResendTimeout(int minisecs) {
_autoResendTimeout = minisecs;
}
/** Returns the timeout, in milliseconds, for re-sending the AU request when
* the server's service unavailable or timeout.
* @since 6.5.2
* <p>Default: 200
*/
public int getAutoResendTimeout() {
return _autoResendTimeout;
}
/** Returns whether this Web application can be crawled by search engines.
* Notice that there is some performance loss for huge web pages.
* <p>Default: false.
* @since 5.0.0
*/
public boolean isCrawlable() {
return _crawlable;
}
/** Sets whether this Web application is crawlable.
* Make a Web application that allows search engines to crawl the application.
* Notice that there is some performance loss for huge web pages. (EE only)
* @since 5.0.0
*/
public void setCrawlable(boolean crawlable) {
if (crawlable && !WebApps.getFeature("ee")) {
log.warn("The crawlable setting is for EE edtion only!");
return;
}
_crawlable = crawlable;
}
/** Returns the timeout URI for this device.
* It is used to show the error message if the desktop being requested
* is not found. It is usually caused by session timeout.
*
* <p>Default: null (to shown an error message).
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @since 3.6.3
*/
public URIInfo getTimeoutURI(String deviceType) {
if (deviceType == null)
deviceType = "ajax";
TimeoutURIInfo inf = _timeoutURIs.get(deviceType);
return inf != null && inf.uri != null ? inf : null;
}
/** Sets the timeout URI.
* It is used to show the error message if the desktop being requested
* is not found. It is usually caused by session timeout.
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @param timeoutURI the timeout URI. If empty, it means to reload
* the same page. If null, an error message is shown instead of
* redirecting to another page.
* @param type how to handle the timeout URI. It is one of
* {@link URIInfo#SEND_REDIRECT} or {@link URIInfo#POPUP}.
* However, it supports only {@link URIInfo#SEND_REDIRECT} currently.
* @return the previous timeout URI, or null if not available.
* @since 3.6.3
*/
public URIInfo setTimeoutURI(String deviceType, String timeoutURI, int type) {
if (deviceType == null)
deviceType = "ajax";
TimeoutURIInfo newi = new TimeoutURIInfo(timeoutURI, type);
TimeoutURIInfo oldi = _timeoutURIs.put(deviceType, newi);
if (oldi != null)
newi.auto = oldi.auto;
return oldi != null && oldi.uri != null ? oldi : null;
}
/** Returns the timeout message for this device, or null if the default
* message is preferred.
* It is used only if {@link #getTimeoutURI} returns null.
* @since 5.0.5
* @see #setTimeoutMessage
*/
public String getTimeoutMessage(String deviceType) {
if (deviceType == null)
deviceType = "ajax";
TimeoutURIInfo inf = _timeoutURIs.get(deviceType);
return inf != null ? inf.message : null;
}
/** Sets the timeout message for this device, or null if the default
* message is preferred.
* It is used only if {@link #getTimeoutURI} returns null.
* <p>To specify an I18N label, prefix the key with <code>label:</code>.
* To specify the JavaScript code, prefix the code with <code>script:</code>.
* Refer to <a href="http://books.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_session-config_Element#The_timeout-message_Element">ZK Configuration Reference</a>
* for more information.
* @return the previous message, if any
* @since 5.0.5
*/
public String setTimeoutMessage(String deviceType, String message) {
if (deviceType == null)
deviceType = "ajax";
TimeoutURIInfo inf = _timeoutURIs.get(deviceType);
if (inf != null) {
String old = inf.message;
inf.message = message;
return old;
}
inf = new TimeoutURIInfo();
inf.message = message;
_timeoutURIs.put(deviceType, inf);
return null;
}
/** Returns whether to automatically trigger the timeout at the client.
* Refer to {@link #setAutomaticTimeout} for details.
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @see #setAutomaticTimeout
* @see #getTimeoutURI
* @since 3.6.3
*/
public boolean isAutomaticTimeout(String deviceType) {
if (deviceType == null)
deviceType = "ajax";
TimeoutURIInfo inf = _timeoutURIs.get(deviceType);
return inf != null && inf.auto;
}
/** Sets whether to automatically trigger the timeout at the client.
*
* <p>Default: false. It means this page is redirected to the timeout URI
* when the use takes some action after timeout. In other words,
* nothing happens if the user does nothing.
* If it is set to true, it is redirected as soon as the timeout URI,
* no matter the user takes any action.
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @see #setTimeoutURI
* @since 3.6.3
*/
public boolean setAutomaticTimeout(String deviceType, boolean auto) {
if (deviceType == null)
deviceType = "ajax";
TimeoutURIInfo inf = _timeoutURIs.get(deviceType);
if (inf != null) {
boolean old = inf.auto;
inf.auto = auto;
return old;
}
inf = new TimeoutURIInfo();
inf.auto = auto;
_timeoutURIs.put(deviceType, inf);
return false;
}
/** Sets the URI to redirect to, when ZK Client Engine receives
* an error.
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @param errCode the error code.
* @param uri the URI to redirect to. It cannot be null.
* If empty, the client will reload the same page again.
* If null, it is the same as {@link #removeClientErrorReload}
* @param connType the connection type: au or server-push.
* If null, "au" is assumed.
* @return the previous URI associated with the specified error code
* @since 3.6.3
*/
public String setClientErrorReload(String deviceType, int errCode, String uri, String connType) {
if (uri == null)
return removeClientErrorReload(deviceType, errCode, connType);
final String index = deviceConn2Str(deviceType, connType);
synchronized (_errURIs) {
Map<Integer, String> map = _errURIs.get(index);
if (map == null)
_errURIs.put(index, map = new LinkedHashMap<Integer, String>());
return map.put(errCode, uri);
}
}
/** Removes the URI to redirect to, when ZK Client Engine receives
* an error.
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @param errCode the error code.
* @param connType the connection type: au or server-push.
* If null, "au" is assumed.
* @return the previous URI associated with the specified error code
* @since 3.6.3
*/
public String removeClientErrorReload(String deviceType, int errCode, String connType) {
final String index = deviceConn2Str(deviceType, connType);
synchronized (_errURIs) {
Map<Integer, String> map = _errURIs.get(index);
return map != null ? map.remove(errCode) : null;
}
}
/** Returns the URI that is associated with the specified error code,
* or null if no URI is associated.
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @param errCode the error code.
* @param connType the connection type: au or server-push.
* If null, "au" is assumed.
* @since 3.6.3
*/
public String getClientErrorReload(String deviceType, int errCode, String connType) {
final String index = deviceConn2Str(deviceType, connType);
synchronized (_errURIs) {
Map<Integer, String> map = _errURIs.get(index);
return map != null ? map.get(errCode) : null;
}
}
/** Returns an array of pairs of the error code and URI info of
* the specified device and connection (never null).
*
* <p>Default: none (none since 3.6.0, while
* older version: 302, 401 and 403 are associated with an empty URI).
*
* @param deviceType the device type: ajax or mil.
* If null, ajax is assumed.
* @param connType the connection type: au or server-push.
* If null, "au" is assumed.
* @return an array of pairs (two-element arrays) of the error code and URI info.
* In other words, each element of the returned array is a pair of
* Integer and {@link URIInfo}.
* For example, [[410, new URIInfo("/login.zul")], [310, new URIInfo("/login2.zul")]].
* @since 3.6.3
*/
public Object[][] getClientErrorReloads(String deviceType, String connType) {
final String index = deviceConn2Str(deviceType, connType);
synchronized (_errURIs) {
final Map<Integer, String> map = _errURIs.get(index);
if (map != null) {
Object[][] infs = new Object[map.size()][2];
int j = 0;
for (Map.Entry<Integer, String> me : map.entrySet()) {
infs[j][0] = me.getKey();
infs[j++][1] = new URIInfo(me.getValue());
}
return infs;
}
}
return new Object[0][0];
}
private static final String deviceConn2Str(String deviceType, String connType) {
if (deviceType == null)
deviceType = "ajax";
return connType != null && "server-push".equals(connType) ? "s:" + deviceType : deviceType;
}
/** Specifies the time, in seconds, between client requests
* before ZK will invalidate the session.
*
* <p>Default: 0 (means the system default).
*
* @see #setTimerKeepAlive
* @see Session#setMaxInactiveInterval
*/
public void setSessionMaxInactiveInterval(int secs) {
_sessTimeout = secs;
}
/** Returns the time, in seconds, between client requests
* before ZK will invalidate the session.
*
* <p>Default: 0 (means the system default).
*
* <p>A negative value indicates that there is no limit.
* Zero means to use the system default (usually defined in web.xml).
*
* @see #isTimerKeepAlive
* @see Session#getMaxInactiveInterval
*/
public int getSessionMaxInactiveInterval() {
return _sessTimeout;
}
/** Specifies the maximal allowed number of desktop
* per session.
*
* <p>Default: 15.
*
* <p>A negative value indicates there is no limit.
* @since 3.0.1
*/
public void setSessionMaxDesktops(int max) {
_sessDktMax = max;
}
/** Returns the maximal allowed number of desktop per session.
*
* <p>A negative value indicates there is no limit.
* @since 3.0.1
*/
public int getSessionMaxDesktops() {
return _sessDktMax;
}
/** Specifies the maximal allowed number of concurrent requests
* per session.
*
* <p>Default: 5.
*
* <p>A negative value indicates there is no limit, but it is
* not recommended due to the possibility of the DoS attacks.
* @since 3.0.1
*/
public void setSessionMaxRequests(int max) {
_sessReqMax = max;
}
/** Returns the maximal allowed number of concurrent requests
* per session.
*
* <p>A negative value indicates there is no limit, but it is
* not recommended due to the possibility of the DoS attacks.
* @since 3.0.1
*/
public int getSessionMaxRequests() {
return _sessReqMax;
}
/** Specifies the maximal allowed number of concurrent server-pushes
* per session.
*
* <p>Default: -1 (no limitation).
*
* @param max the maximal allowed number.
* A negative value indicates there is no limit.
* @since 5.0.0
*/
public void setSessionMaxPushes(int max) {
_sessPushMax = max;
}
/** Returns the maximal allowed number of concurrent server-pushes
* per session.
*/
public int getSessionMaxPushes() {
return _sessPushMax;
}
/** Specifies the maximal allowed number of the spare pool for
* queuing the event processing threads (per Web application).
*
* <p>Default: 100.
*
* <p>A negative value indicates there is no limit.
*
* <p>ZK uses a thread pool to keep the idle event processing threads.
* It speeds up the service of an event by reusing the thread queued
* in this pool.
*
* @see #setMaxSuspendedThreads
* @see #isEventThreadEnabled
*/
public void setMaxSpareThreads(int max) {
_sparThdMax = max;
}
/** Returns the maximal allowed number of the spare pool for
* queuing event processing threads (per Web application).
* @see #isEventThreadEnabled
*/
public int getMaxSpareThreads() {
return _sparThdMax;
}
/** Specifies the maximal allowed number of suspended event
* processing threads (per Web application).
*
* <p>Default: -1 (no limit).
*
* <p>A negative value indicates there is no limit.
*
* <p>It is ignored if the use of the event processing thread
* is disable ({@link #isEventThreadEnabled}.
*/
public void setMaxSuspendedThreads(int max) {
_suspThdMax = max;
}
/** Returns the maximal allowed number of suspended event
* processing threads (per Web application).
*
* <p>It is ignored if the use of the event processing thread
* is disable ({@link #isEventThreadEnabled}.
* @see #isEventThreadEnabled
*/
public int getMaxSuspendedThreads() {
return _suspThdMax;
}
/** Sets whether to use the event processing thread.
*
* <p>Default: false (disabled).
*
* @exception IllegalStateException if there is suspended thread
* and use is false.
* @deprecated as of release 10.0.0. (not recommended to use in production)
*/
public void enableEventThread(boolean enable) {
if (!enable && _wapp != null) {
final UiEngine engine = ((WebAppCtrl) _wapp).getUiEngine();
if (engine != null) {
if (engine.hasSuspendedThread())
throw new IllegalStateException("Unable to disable due to suspended threads");
}
}
if (enable)
log.warn("Enable event thread has deprecated!");
_evtThdEnabled = enable;
}
/** Returns whether to use the event processing thread.
* <p>Default: false (disabled).
* @deprecated as of release 10.0.0. (not recommended to use in production)
*/
public boolean isEventThreadEnabled() {
return _evtThdEnabled;
}
/** Sets whether zscript is allowed.
* <p>Default: true (enabled).
* @since 6.0.0
*/
public void enableZScript(boolean enable) {
_zscriptEnabled = enable;
}
/** Returns whether zscript is allowed.
* <p>Default: true (enabled).
* @since 6.0.0
*/
public boolean isZScriptEnabled() {
return _zscriptEnabled;
}
/** Returns the monitor for this application, or null if not set.
*/
public Monitor getMonitor() {
return _monitor;
}
/** Sets the monitor for this application, or null to disable it.
*
* <p>Default: null.
*
* <p>There is at most one monitor for each Web application.
* The previous monitor will be replaced when this method is called.
*
* <p>In addition to call this method, you could specify a monitor
* in zk.xml
*
* @param monitor the performance meter. If null, the meter function
* is disabled.
* @return the previous monitor, or null if not available.
*/
public Monitor setMonitor(Monitor monitor) {
final Monitor old = _monitor;
_monitor = monitor;
return old;
}
/** Returns the performance meter for this application, or null if not set.
* @since 3.0.0
*/
public PerformanceMeter getPerformanceMeter() {
return _pfmeter;
}
/** Sets the performance meter for this application, or null to disable it.
*
* <p>Default: null.
*
* <p>There is at most one performance meter for each Web application.
* The previous meter will be replaced when this method is called.
*
* <p>In addition to call this method, you could specify
* a performance meter in zk.xml
*
* @param meter the performance meter. If null, the meter function
* is disabled.
* @return the previous performance meter, or null if not available.
* @since 3.0.0
*/
public PerformanceMeter setPerformanceMeter(PerformanceMeter meter) {
final PerformanceMeter old = _pfmeter;
_pfmeter = meter;
return old;
}
/** Returns the execution monitor for this application, or null if not set.
* @since 6.0.0
*/
public ExecutionMonitor getExecutionMonitor() {
return _execmon;
}
/** Sets the execution monitor for this application, or null to disable it.
*
* <p>Default: null.
*
* <p>There is at most one execution monitor for each Web application.
* The previous meter will be replaced when this method is called.
*
* <p>In addition to call this method, you could specify
* an execution monitor in zk.xml
*
* @param monitor the execution monitor. If null, the monitor function
* is disabled.
* @return the previous execution monitor, or null if not available.
* @since 6.0.0
*/
public ExecutionMonitor setExecutionMonitor(ExecutionMonitor monitor) {
final ExecutionMonitor old = _execmon;
_execmon = monitor;
return old;
}
/** Returns the desktop recycle for this application, or null if not set.
* @since 5.0.0
*/
public DesktopRecycle getDesktopRecycle() {
return _dtRecycle;
}
/** Sets the desktop recycler for this application, or null to disable it.
*
* <p>Default: null.
*
* <p>There is at most one desktop recycle for each Web application.
* The previous instance will be replaced when this method is called.
*
* <p>In addition to call this method, you could specify
* a desktop recycle in zk.xml
*
* @param dtRecycle the desktop recycle. If null, the recycle function
* is disabled.
* @return the previous desktop recycle, or null if not available.
* @since 5.0.0
*/
public DesktopRecycle setDesktopRecycle(DesktopRecycle dtRecycle) {
final DesktopRecycle old = _dtRecycle;
_dtRecycle = dtRecycle;
return old;
}
/** Returns the charset used to generate the HTTP response
* or null to use the container's default.
* It is currently used by {@link org.zkoss.zk.ui.http.DHtmlLayoutServlet},
*
* <p>Default: UTF-8.
*/
public String getResponseCharset() {
return _charsetResp;
}
/** Sets the charset used to generate HTTP response.
* It is currently used by {@link org.zkoss.zk.ui.http.DHtmlLayoutServlet},
*
* @param charset the charset to use. If null or empty, the container's default
* is used.
*/
public void setResponseCharset(String charset) {
_charsetResp = charset != null && charset.length() > 0 ? charset : null;
}
/** Returns the value of the preference defined in zk.xml, or by
* {@link #setPreference}.
*
* <p>Preference is application specific. You can specify whatever you want
* as you specifying context-param for a Web application.
*
* @param defaultValue the default value that is used if the specified
* preference is not found.
*/
public String getPreference(String name, String defaultValue) {
final String value = _prefs.get(name);
return value != null ? value : defaultValue;
}
/** Sets the value of the preference.
*/
public void setPreference(String name, String value) {
if (name == null || value == null)
throw new IllegalArgumentException("null");
_prefs.put(name, value);
}
/** Returns a readonly set of all preference names.
*/
public Set<String> getPreferenceNames() {
return _prefs.keySet();
}
/** Adds the definition of a richlet.
*
* <p>If there was a richlet associated with the same name, the
* the old richlet will be replaced.
*
* @param name the richlet name
* @param params the initial parameters, or null if no initial parameter at all.
* Once called, the caller cannot access <code>params</code> any more.
* @return the previous richlet class or class-name with the specified name,
* or null if no previous richlet.
*/
public Object addRichlet(String name, Class<?> richletClass, Map<String, String> params) {
if (!Richlet.class.isAssignableFrom(richletClass))
throw new IllegalArgumentException(
"A richlet class, " + richletClass + ", must implement " + Richlet.class.getName());
return addRichlet0(name, richletClass, params);
}
/** Adds the definition of a richlet.
*
* <p>If there was a richlet associated with the same name, the
* the old servlet will be replaced.
*
* @param name the richlet name
* @param richletClassName the class name. The class will be loaded
* only when the richlet is loaded.
* @param params the initial parameters, or null if no initial parameter at all.
* Once called, the caller cannot access <code>params</code> any more.
* @return the previous richlet class or class-name with the specified name,
* or null if no previous richlet.
*/
public Object addRichlet(String name, String richletClassName, Map<String, String> params) {
if (richletClassName == null || richletClassName.length() == 0)
throw new IllegalArgumentException("richletClassName is required");
return addRichlet0(name, richletClassName, params);
}
/** Adds the richlet.
*
* <p>If there was a richlet associated with the same name, the
* the old one will be replaced.
*
* @param name the richlet name
* @param richlet the richlet implemetation.
* @return the previous richlet class or class-name with the specified name,
* or null if no previous richlet.
* @since 7.0.2
*/
public Object addRichlet(String name, Richlet richlet) {
if (richlet == null)
throw new IllegalArgumentException("richlet instance is required");
return addRichlet0(name, richlet, null);
}
private Object addRichlet0(String name, Object richletClass, Map<String, String> params) {
Object o;
for (;;) {
// remove previous richlet if it exists
o = removeRichlet0(name);
synchronized (_richlets) {
// add new richlet definition only if map does not contain record
// with same name
if (!(_richlets.containsKey(name))) {
if (richletClass instanceof Richlet) {
_richlets.put(name, richletClass);
} else {
_richlets.put(name, new Object[] { richletClass, params });
}
break;
}
}
}
return o;
}
/**
* Removes the richlet and associated richlet mappings.
*
* @param name the richlet name
* @return the removed richlet class or class-name with the specified name,
* or null if the richlet is not found.
* @since 7.0.2
*/
public Object removeRichlet(String name) {
// remove richlet
final Object o = removeRichlet0(name);
// remove associated richlet mappings
removeRichletMapping(name);
return o;
}
/**
* Removes the richlet.
*
* @param name the richlet name
* @return the removed richlet class or class-name with the specified name,
* or null if the richlet is not found.
* @since 7.0.2
*/
private Object removeRichlet0(String name) {
if (name == null) {
throw new IllegalArgumentException("Name is required");
}
Object o;
for (;;) {
// remove richlet
synchronized (_richlets) {
o = _richlets.remove(name);
}
// verify it sth instancing richlet at the moment
if (o instanceof WaitLock) {
WaitLock lock = (WaitLock) o;
if (!lock.waitUntilUnlock(300 * 1000)) { //5 minute
String msg = new StringBuilder("Unable to remove richlet ").append(name)
.append("\nCause: conflict too long.").toString();
final PotentialDeadLockException ex = new PotentialDeadLockException(msg);
log.warn(msg, ex); //very rare, possibly a bug
throw ex;
}
} else {
break;
}
}
if (o == null) {
return null;
}
if (o instanceof Richlet) {
// destroy object if it is richlet
destroy((Richlet) o);
return o.getClass();
}
return ((Object[]) o)[0];
}
/** Adds a richlet mapping.
*
* @param name the name of the richlet.
* @param path the URL pattern. It must start with '/' and may end
* with '/*'.
* @exception UiException if the richlet is not defined yet.
* See {@link #addRichlet}.
* @since 2.4.0
*/
public void addRichletMapping(String name, String path) {
//Note: "/" is the same as ""
if (path == null || path.length() == 0 || "/".equals(path))
path = "";
else if (path.charAt(0) != '/')
throw new IllegalArgumentException("path must start with '/', not " + path);
final boolean wildcard = path.endsWith("/*");
if (wildcard) //wildcard
path = path.substring(0, path.length() - 2);
//note it might be empty
//richlet mapping cannot be added if richlet is not defined,
//so check if richlet with same name exists and then
//add richlet mapping
synchronized (_richlets) {
if (!_richlets.containsKey(name))
throw new UiException("Richlet not defined: " + name);
synchronized (_richletmaps) {
_richletmaps.put(path, new Object[] { name, Boolean.valueOf(wildcard) });
}
}
}
/**
* Removes all richlet mappings for the specified richlet.
*
* @param name the richlet name
*/
private void removeRichletMapping(String name) {
// remove richlet mapping
synchronized (_richletmaps) {
Iterator<Map.Entry<String, Object[]>> iter = _richletmaps.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Object[]> entry = iter.next();
String richletName = (String) entry.getValue()[0];
if (richletName.equals(name)) {
iter.remove();
}
}
}
}
private static void destroy(Richlet richlet) {
try {
richlet.destroy();
} catch (Throwable ex) {
log.error("Unable to destroy " + richlet);
}
}
/** Returns an instance of richlet of the specified name, or null
* if not found.
*/
@SuppressWarnings("unchecked")
public Richlet getRichlet(String name) {
WaitLock lock = null;
final Object[] info;
for (;;) {
synchronized (_richlets) {
Object o = _richlets.get(name);
if (o == null || (o instanceof Richlet)) { //not found or loaded
return (Richlet) o;
} else if (o instanceof WaitLock) { //loading by another thread
lock = (WaitLock) o;
} else {
info = (Object[]) o;
//going to load in this thread
_richlets.put(name, lock = new WaitLock());
break; //then, load it
}
} //sync(_richlets)
if (!lock.waitUntilUnlock(300 * 1000)) { //5 minute
final PotentialDeadLockException ex = new PotentialDeadLockException(
"Unable to load richlet " + name + "\nCause: conflict too long.");
log.warn("", ex); //very rare, possibly a bug
throw ex;
}
} //for (;;)
//load it
try {
if (info[0] instanceof String) {
try {
info[0] = Classes.forNameByThread((String) info[0]);
} catch (Throwable ex) {
throw new UiException("Failed to load " + info[0]);
}
}
final Object o = ((Class<?>) info[0]).newInstance();
if (!(o instanceof Richlet))
throw new UiException(Richlet.class + " must be implemented by " + info[0]);
final Richlet richlet = (Richlet) o;
richlet.init(newRichletConfig((Map<String, String>) info[1]));
synchronized (_richlets) {
_richlets.put(name, richlet);
}
return richlet;
} catch (Throwable ex) {
synchronized (_richlets) {
_richlets.put(name, info); //remove lock and restore info
}
throw UiException.Aide.wrap(ex, "Unable to instantiate " + info[0]);
} finally {
lock.unlock();
}
}
private RichletConfig newRichletConfig(Map<String, String> params) {
return new RichletConfigImpl(_wapp, params);
}
/** Returns an instance of richlet for the specified path, or
* null if not found.
*/
public Richlet getRichletByPath(String path) {
if (path == null || path.length() == 0 || "/".equals(path))
path = "";
else if (path.charAt(0) != '/')
path = '/' + path;
final int len = path.length();
for (int j = len;;) {
final Richlet richlet = getRichletByPath0(path.substring(0, j), j != len);
if (richlet != null || j == 0)
return richlet;
j = path.lastIndexOf('/', j - 1); //j must not -1
}
}
private Richlet getRichletByPath0(String path, boolean wildcardOnly) {
final Object[] info;
synchronized (_richletmaps) {
info = _richletmaps.get(path);
}
return info != null && (!wildcardOnly || ((Boolean) info[1]).booleanValue()) ? getRichlet((String) info[0])
: null;
}
/** Destroys all richlets.
*/
public void detroyRichlets() {
synchronized (_richlets) {
for (Iterator<Object> it = _richlets.values().iterator(); it.hasNext();) {
final Object o = it.next();
if (o instanceof Richlet)
destroy((Richlet) o);
}
_richlets.clear();
}
}
/** Specifies whether to keep the desktops across visits.
* If false, the desktops are removed when an user reloads an URL
* or browses to another URL.
*
* <p>Default: false.
*/
public void setKeepDesktopAcrossVisits(boolean keep) {
_keepDesktop = keep;
}
/** Returns whether to keep the desktops across visits.
* If false, the desktops are removed when an user reloads an URL
* or browses to another URL.
*/
public boolean isKeepDesktopAcrossVisits() {
return _keepDesktop;
}
/** Specifies whether to keep the session alive,
* when receiving the onTimer event.
*
* <p>Default: false.
*
* <p>A session is expired (and then invalidated), if it didn't receive
* any client request in the specified timeout interval
* ({@link #getSessionMaxInactiveInterval}).
* By setting this option to true, the session timeout will be reset
* when onTimer is received (just like any other event).
*
* <p>Note: if true and the timer is shorter than
* the session timeout ({@link #getSessionMaxInactiveInterval}),
* the session is never expired.
*
* @param alive whether to keep the session alive when receiving
* onTimer
* @since 3.0.0
*/
public void setTimerKeepAlive(boolean alive) {
_timerKeepAlive = alive;
}
/** Returns whether to keep the session alive,
* when receiving the onTimer event.
* In other words, it returns whether to reset the session timeout
* counter when receiving onTimer, just like any other events.
*
* @since 3.0.0
*/
public boolean isTimerKeepAlive() {
return _timerKeepAlive;
}
/** Returns whether to debug JavaScript files.
* If true, it means the original (i.e., uncompressed) JavaScript files
* shall be loaded instead of the compressed JavaScript files.
*
* @since 3.0.4
* @see #setDebugJS
*/
public boolean isDebugJS() {
return _debugJS;
}
/**Sets whether to debug JavaScript files.
*
* <p>Default: false.
*
* <p>If true is specified, it will try to load the original
* Java (i.e., uncompressed) file instead of the compressed one.
* For example, if {@link org.zkoss.web.util.resource.ClassWebResource#service} is called to load abc.js,
* and {@link #isDebugJS}, then {@link org.zkoss.web.util.resource.ClassWebResource#service} will try
* to load abc.org.js first. If not found, it load ab.js insted.
*
* <p>If {@link #isDebugJS} is false (default),
* abc.js is always loaded.
*
* <p>Prior to 5.0.3, the setting won't affect JavaScript files that have been
* loaded. That is, the reboot is required.
*
* @param debug whether to debug JavaScript files.
* If true, the original JavaScript files shall be
* loaded instead of the compressed files.
* @since 3.0.4
*/
public void setDebugJS(boolean debug) {
_debugJS = debug;
if (_wapp != null)
org.zkoss.zk.ui.http.Utils.updateDebugJS(_wapp, debug);
}
/** Sets the implementation of the expression factory that shall
* be used by the whole system.
*
* <p>Default: null -- it means the org.zkoss.xel.el.ELFactory class
* (it requires zcommons-el.jar).
*
* <p>Note: you can only specify an implementation that is compatible
* with JSP EL here, since ZK's builtin pages depend on it.
* However, you can use any factory you like in an individual page,
* as long as all expressions in the page follow the syntax of
* the evaluator you are using.
*
* @param expfcls the implemtation class, or null to use the default.
* Note: expfcls must implement {@link ExpressionFactory}.
* @since 3.0.0
*/
public void setExpressionFactoryClass(Class<? extends ExpressionFactory> expfcls) {
Expressions.setExpressionFactoryClass(expfcls);
}
/** Returns the implementation of the expression factory that
* is used by the whole system, or null if the system default is used.
*
* @see #setExpressionFactoryClass
* @since 3.0.0
*/
public Class<? extends ExpressionFactory> getExpressionFactoryClass() {
return Expressions.getExpressionFactoryClass();
}
/** Invokes {@link EventInterceptor#beforeSendEvent}
* registered by {@link #addListener} with a class implementing
* {@link EventInterceptor}.
* <p>Used only internally.
* @since 3.0.0
*/
public Event beforeSendEvent(Event event) {
return _eis.beforeSendEvent(event);
}
/** Invokes {@link EventInterceptor#beforePostEvent}
* registered by {@link #addListener} with a class implementing
* {@link EventInterceptor}.
* <p>Used only internally.
* @since 3.0.0
*/
public Event beforePostEvent(Event event) {
return _eis.beforePostEvent(event);
}
/** Invokes {@link EventInterceptor#beforeProcessEvent}
* registered by {@link #addListener} with a class implementing
* {@link EventInterceptor}.
* <p>Used only internally.
* @since 3.0.0
*/
public Event beforeProcessEvent(Event event) {
return _eis.beforeProcessEvent(event);
}
/** Invokes {@link EventInterceptor#afterProcessEvent}
* registered by {@link #addListener} with a class implementing
* {@link EventInterceptor}.
* <p>Used only internally.
* @since 3.0.0
*/
public void afterProcessEvent(Event event) {
_eis.afterProcessEvent(event);
}
/** Returns a map of application-specific attributes.
* @since 5.0.0
*/
public Map<String, Object> getAttributes() {
return _attrs;
}
/** Returns the value of an application-specific attribute, or
* null if not found.
* @since 5.0.0
*/
public Object getAttribute(String name) {
return _attrs.get(name);
}
/** Returns the value of an application-specific attribute.
* @param value the value of the attribute. If null, it means removal,
* i.e., {@link #removeAttribute}.
* @return the previous value, or null if no such value.
* @since 5.0.0
*/
public Object setAttribute(String name, Object value) {
return value != null ? _attrs.put(name, value) : removeAttribute(name);
}
/** Removes the value of an application-specific attribute.
* @return the previous value, or null if no such value.
* @since 5.0.0
*/
public Object removeAttribute(String name) {
return _attrs.remove(name);
}
/** Adds a client (JavaScript) package that is provided by this server.
* <p>Default: none.
* <p>If no package is defined (default), ZK Client Engine assumes
* all packages coming from the server generating the HTML page.
*
* <p>However, it might not be true if you want to load some client
* codes from different server (such as Ajax-asService).
* Therefore, you have to invoke this method to add the client packages
* if this server is going to provide JavaScript codes for other servers.
* @since 5.0.0
*/
public void addClientPackage(String pkg) {
if (pkg == null || pkg.length() == 0)
throw new IllegalArgumentException("empty");
_clientpkgs.add(pkg);
}
/** Returns a readonly list of the names of the client pages
* that are provided by this server
*
* @since 5.0.0
*/
public String[] getClientPackages() {
return _clientpkgs.toArray();
}
/** Returns the time, in seconds, to show a warning message
* if an event has been processinged longer than it.
* <p>Default: 600
* @since 3.6.3
*/
public int getEventTimeWarning() {
return _evtTimeWarn;
}
/** Set the time, in seconds, to show a warning message
* if an event has been processinged longer than it.
* @param secs the number of seconds.
* If a non-positive number is specified, no warning message at all.
* @since 3.6.3
*/
public void setEventTimeWarning(int secs) {
_evtTimeWarn = secs;
}
/** Adds an error page.
*
* @param deviceType the device type: ajax or mil
* @param type what type of errors the error page is associated with.
* @param location where is the error page.
* @return the previous location of the same error, or null if not
* defined yet.
* @since 2.4.1
*/
public String addErrorPage(String deviceType, Class<?> type, String location) {
if (!Throwable.class.isAssignableFrom(type))
throw new IllegalArgumentException("Throwable or derived is required: " + type);
if (location == null || deviceType == null)
throw new IllegalArgumentException();
List<ErrorPage> l;
synchronized (_errpgs) {
l = _errpgs.get(deviceType);
if (l == null)
_errpgs.put(deviceType, l = new LinkedList<ErrorPage>());
}
String previous = null;
synchronized (l) {
//remove the previous definition
for (Iterator<ErrorPage> it = l.iterator(); it.hasNext();) {
final ErrorPage errpg = it.next();
if (errpg.type.equals(type)) {
previous = errpg.location;
it.remove();
break;
}
}
l.add(new ErrorPage(type, location));
}
return previous;
}
/** Returns the error page that matches the specified error, or null if not found.
*
* @param deviceType the device type: ajax or mil
* @param error the exception being thrown
* @since 2.4.1
*/
public String getErrorPage(String deviceType, Throwable error) {
if (!_errpgs.isEmpty()) {
final List<ErrorPage> l;
synchronized (_errpgs) {
l = _errpgs.get(deviceType);
}
if (l != null) {
synchronized (l) {
for (Iterator<ErrorPage> it = l.iterator(); it.hasNext();) {
final ErrorPage errpg = it.next();
if (errpg.type.isInstance(error))
return errpg.location;
}
}
}
}
return null;
}
private static class ErrorPage {
private final Class<?> type;
private final String location;
private ErrorPage(Class<?> type, String location) {
this.type = type;
this.location = location;
}
}
/** Used with {@link FastReadArray} to check if an object is
* the same class as specified.
*/
private static class SameClass implements Comparable<Object> {
private final Class<?> _klass;
private SameClass(Class<?> klass) {
_klass = klass;
}
public int compareTo(Object o) {
return o.getClass().equals(_klass) ? 0 : 1;
}
//Object//
public String toString() {
return Objects.toString(_klass);
}
public boolean equals(Object o) {
if (this == o)
return true;
return Objects.equals(_klass, o instanceof SameClass ? ((SameClass) o)._klass : o);
}
public int hashCode() {
return Objects.hashCode(_klass);
}
}
private static class TimeoutURIInfo extends URIInfo {
private String message;
private boolean auto;
private TimeoutURIInfo() {
super(null);
}
private TimeoutURIInfo(String uri, int type) {
super(uri, type);
}
}
/**
* Returns whether to use custom {@link ThemeProvider}. If true, it means
* the default ThemeProvider shall be ignored. It is set to true if declare
* custom ThemeProvider in zk.xml
* <p>
* Default: false
* @since 7.0.0
*/
public boolean isCustomThemeProvider() {
return _customThemeProvider;
}
/**
* Sets whether to use custom {@link ThemeProvider}.
* <p>Default: false.
* <p>It is set to true if declare custom ThemeProvider in zk.xml
* <p>Note: internal use only
* @param customThemeProvider whether to use custom {@link ThemeProvider}.
* @since 7.0.0
*/
public void setCustomThemeProvider(boolean customThemeProvider) {
_customThemeProvider = customThemeProvider;
}
/**
* Returns whether to use custom {@link ThemeRegistry}. If true, it means
* the default ThemeRegistry shall be ignored. It is set to true if declare
* custom ThemeRegistry in zk.xml
* <p>Default: false
* @since 7.0.0
*/
public boolean isCustomThemeRegistry() {
return _customThemeRegistry;
}
/**
* Sets whether to use custom {@link ThemeRegistry}.
* <p>Default: false.
* <p>It is set to true if declare custom ThemeRegistry in zk.xml.
* <p>Note: internal use only
* @param customThemeRegistry whether to use custom {@link ThemeRegistry}.
* @since 7.0.0
*/
public void setCustomThemeRegistry(boolean customThemeRegistry) {
_customThemeRegistry = customThemeRegistry;
}
/**
* Returns whether to use custom {@link ThemeResolver}. If true, it means
* the default ThemeResolver shall be ignored. It is set to true if declare
* custom ThemeResolver in zk.xml
* <p>Default: false
* @since 7.0.0
*/
public boolean isCustomThemeResolver() {
return _customThemeResolver;
}
/**
* Sets whether to use custom {@link ThemeResolver}.
* <p>Default: false.
* <p>It is set to true if declare custom ThemeResolver in zk.xml.
* <p>Note: internal use only
* @param customThemeResolver whether to use custom {@link ThemeResolver}.
* @since 7.0.0
*/
public void setCustomThemeResolver(boolean customThemeResolver) {
_customThemeResolver = customThemeResolver;
}
/**
* Sets user customized init crash script
* <p>User can customize init crash page layout by defining a javascript function which is assigned to <code>window.zkShowCrashMessage</code>
* @param script
* @since 7.0.4
*/
public void setInitCrashScript(String script) {
_initCrashScript = script;
}
/**
* Sets user customized init crash timeout
* <p>User can customize init crash timeout by simply giving a number(sec).
* @param timeout
* @since 7.0.4
*/
public void setInitCrashTimeout(int timeout) {
_initCrashTimeout = timeout;
}
/**
* Returns init crash script, if null, use default, see crashmsg.js
* @return String
* @since 7.0.4
*/
public String getInitCrashScript() {
return _initCrashScript;
}
/**
* Returns init crash timeout, if -1, use default, which is 60 sec
* @return int
* @since 7.0.4
*/
public int getInitCrashTimeout() {
return _initCrashTimeout;
}
/**
* Returns binder init attribute name
* @return String
* @since 8.0.0
*/
public String getBinderInitAttribute() {
return _binderInitAttribute;
}
/**
* Set binder init attribute name
* @since 8.0.0
*/
public void setBinderInitAttribute(String binderInitAttribute) {
this._binderInitAttribute = binderInitAttribute;
}
/**
* Returns binder annotations
* @return Set
* @since 8.0.0
*/
public Set<String> getBinderAnnotations() {
return _binderAnnotations;
}
/**
* Set binder annotations
* @since 8.0.0
*/
public void setBinderAnnotations(Set<String> binderAnnotations) {
this._binderAnnotations = new HashSet<String>(binderAnnotations);
}
/**
* Adds client data attribute handler
* @since 8.0.0
*/
public void addDataHandler(DataHandlerInfo info) {
final String name = info.getName();
DataHandlerInfo old = _dataHandlers.put(name, info);
if (info.isOverride()) {
if (old == null)
log.warn("The data handler cannot be overridden! Not existing: [" + name + "]");
} else {
if (old != null)
log.warn("The data handler has been defined! [" + name
+ "]\n Please use <override>true</override> to disable the warning.");
}
}
/**
* Returns all of the client data attribute handlers
* @since
*/
public Map<String, DataHandlerInfo> getDataHandlers() {
return _dataHandlers;
}
/** Sets whether source map is enabled.
* <p>Default: false (disabled).
* @since 8.5.0
*/
public void enableSourceMap(boolean enable) {
_sourceMapEnabled = enable;
}
/** Returns whether source map is enabled.
* <p>Default: false (disabled).
* @since 8.5.0
*/
public boolean isSourceMapEnabled() {
return _sourceMapEnabled;
}
/** Sets whether history state handle is enabled.
* If disabled, ZK won't generate any onHistoryPopState event even if history state is popped.
* It is desirable to avoid unnecessary events back to server if history state is handled by front-end.
* <p>Default: true (enabled).
* @since 8.6.1
*/
public void enableHistoryState(boolean enable) {
_historyStateEnabled = enable;
}
/** Returns whether history state handle is enabled.
* <p>Default: true (enabled).
* @since 8.6.1
*/
public boolean isHistoryStateEnabled() {
return _historyStateEnabled;
}
/**
* Sets whether to send client errors to the server for logging
* the page url where the error occurred and its stack trace.
* <p>Default: false.</p>
* @since 10.0.0
*/
public void setSendClientErrors(boolean send) {
_sendClientErrors = send;
}
/**
* Returns whether to send client errors to the server for logging
* the page url where the error occurred and its stack trace.
* <p>Default: false.</p>
* @since 10.0.0
*/
public boolean isSendClientErrors() {
return _sendClientErrors;
}
}