001: /* ConfigParser.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Sun Mar 26 18:09:10 2006, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2006 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zk.ui.sys;
020:
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.net.URL;
024:
025: import org.zkoss.lang.Classes;
026: import org.zkoss.util.Cache;
027: import org.zkoss.util.resource.Locator;
028: import org.zkoss.util.logging.Log;
029: import org.zkoss.idom.Element;
030: import org.zkoss.idom.input.SAXBuilder;
031: import org.zkoss.idom.util.IDOMs;
032: import org.zkoss.xel.ExpressionFactory;
033: import org.zkoss.web.servlet.http.Encodes;
034:
035: import org.zkoss.zk.ui.WebApp;
036: import org.zkoss.zk.ui.UiException;
037: import org.zkoss.zk.ui.util.Configuration;
038: import org.zkoss.zk.ui.util.CharsetFinder;
039: import org.zkoss.zk.ui.util.ThemeProvider;
040: import org.zkoss.zk.ui.metainfo.DefinitionLoaders;
041: import org.zkoss.zk.scripting.Interpreters;
042: import org.zkoss.zk.device.Devices;
043: import org.zkoss.zk.au.AuWriters;
044:
045: /**
046: * Used to parse WEB-INF/zk.xml into {@link Configuration}.
047: *
048: * @author tomyeh
049: */
050: public class ConfigParser {
051: private static final Log log = Log.lookup(ConfigParser.class);
052:
053: /** Used to provide backward compatibility to 2.3.0's richlet definition. */
054: private int _richletnm;
055:
056: /** Parses zk.xml, specified by url, into the configuration.
057: *
058: * @param url the URL of zk.xml.
059: */
060: public void parse(URL url, Configuration config, Locator locator)
061: throws Exception {
062: if (url == null || config == null)
063: throw new IllegalArgumentException("null");
064: log.info("Parsing " + url);
065: parse(new SAXBuilder(false, false, true).build(url)
066: .getRootElement(), config, locator);
067: }
068:
069: /** Parses zk.xml, specified by the root element.
070: * @since 3.0.1
071: */
072: public void parse(Element root, Configuration config,
073: Locator locator) throws Exception {
074: for (Iterator it = root.getElements().iterator(); it.hasNext();) {
075: final Element el = (Element) it.next();
076: final String elnm = el.getName();
077: if ("listener".equals(elnm)) {
078: final String clsnm = IDOMs.getRequiredElementValue(el,
079: "listener-class");
080: try {
081: final Class cls = Classes.forNameByThread(clsnm);
082: config.addListener(cls);
083: } catch (Throwable ex) {
084: throw new UiException("Unable to load " + clsnm
085: + ", at " + el.getLocator(), ex);
086: }
087: } else if ("richlet".equals(elnm)) {
088: final String clsnm = IDOMs.getRequiredElementValue(el,
089: "richlet-class");
090: final Map params = IDOMs.parseParams(el, "init-param",
091: "param-name", "param-value");
092:
093: String path = el.getElementValue("richlet-url", true);
094: if (path != null) {
095: //deprecated since 2.4.0, but backward compatible
096: final int cnt;
097: synchronized (this ) {
098: cnt = _richletnm++;
099: }
100: final String name = "z_obs_"
101: + Integer.toHexString(cnt);
102: try {
103: config.addRichlet(name, clsnm, params);
104: config.addRichletMapping(name, path);
105: } catch (Throwable ex) {
106: throw new UiException(
107: "Illegal richlet definition at "
108: + el.getLocator(), ex);
109: }
110: } else { //syntax since 2.4.0
111: final String nm = IDOMs.getRequiredElementValue(el,
112: "richlet-name");
113: try {
114: config.addRichlet(nm, clsnm, params);
115: } catch (Throwable ex) {
116: throw new UiException(
117: "Illegal richlet definition at "
118: + el.getLocator(), ex);
119: }
120: }
121: } else if ("richlet-mapping".equals(elnm)) { //syntax since 2.4.0
122: final String nm = IDOMs.getRequiredElementValue(el,
123: "richlet-name");
124: final String path = IDOMs.getRequiredElementValue(el,
125: "url-pattern");
126: try {
127: config.addRichletMapping(nm, path);
128: } catch (Throwable ex) {
129: throw new UiException("Illegal richlet mapping at "
130: + el.getLocator(), ex);
131: }
132: } else if ("desktop-config".equals(elnm)) {
133: //desktop-config
134: // desktop-timeout
135: // disable-theme-uri
136: // file-check-period
137: // theme-provider-class
138: // theme-uri
139: parseDesktopConfig(config, el);
140: parseClientConfig(config, el); //backward compatible with 2.4
141:
142: } else if ("client-config".equals(elnm)) { //since 3.0.0
143: //client-config
144: // disable-behind-modal
145: // keep-across-visits
146: // processing-prompt-delay
147: // error-reload
148: // tooltip-delay
149: // resend-delay
150: parseClientConfig(config, el);
151:
152: } else if ("session-config".equals(elnm)) {
153: //session-config
154: // session-timeout
155: // max-desktops-per-session
156: // max-requests-per-session
157: // timer-keep-alive
158: // timeout-uri (deprecated)
159: Integer v = parseInteger(el, "session-timeout", false);
160: if (v != null)
161: config.setSessionMaxInactiveInterval(v.intValue());
162:
163: v = parseInteger(el, "max-desktops-per-session", false);
164: if (v != null)
165: config.setSessionMaxDesktops(v.intValue());
166:
167: v = parseInteger(el, "max-requests-per-session", false);
168: if (v != null)
169: config.setSessionMaxRequests(v.intValue());
170:
171: String s = el.getElementValue("timer-keep-alive", true);
172: if (s != null)
173: config.setTimerKeepAlive("true".equals(s));
174:
175: //deprecated since 2.4.0, but backward compatible
176: s = el.getElementValue("timeout-uri", true);
177: if (s != null)
178: Devices.setTimeoutURI("ajax", s);
179: } else if ("language-config".equals(elnm)) {
180: //language-config
181: // addon-uri
182: parseLangAddon(locator, el);
183: } else if ("language-mapping".equals(elnm)) {
184: //language-mapping
185: // language-name/extension
186: DefinitionLoaders.addExtension(IDOMs
187: .getRequiredElementValue(el, "extension"),
188: IDOMs.getRequiredElementValue(el,
189: "language-name"));
190: //Note: we don't add it to LanguageDefinition now
191: //since addon-uri might be specified later
192: //(so we cannot load definitions now)
193: } else if ("system-config".equals(elnm)) {
194: //system-config
195: // disable-event-thread
196: // max-spare-threads
197: // max-suspended-threads
198: // max-upload-size
199: // upload-charset
200: // upload-charset-finder-class
201: // max-process-time
202: // response-charset
203: // cache-provider-class
204: // ui-factory-class
205: // failover-manager-class
206: // engine-class
207: // id-generator-class
208: // web-app-class
209: // method-cache-class
210: // url-encoder-class
211: // au-writer-class
212: String s = el.getElementValue("disable-event-thread",
213: true);
214: if (s != null)
215: config.enableEventThread("false".equals(s));
216:
217: Integer v = parseInteger(el, "max-spare-threads", false);
218: if (v != null)
219: config.setMaxSpareThreads(v.intValue());
220:
221: v = parseInteger(el, "max-suspended-threads", false);
222: if (v != null)
223: config.setMaxSuspendedThreads(v.intValue());
224:
225: v = parseInteger(el, "max-upload-size", false);
226: if (v != null)
227: config.setMaxUploadSize(v.intValue());
228:
229: v = parseInteger(el, "max-process-time", true);
230: if (v != null)
231: config.setMaxProcessTime(v.intValue());
232:
233: s = el.getElementValue("upload-charset", true);
234: if (s != null)
235: config.setUploadCharset(s);
236:
237: s = el.getElementValue("response-charset", true);
238: if (s != null)
239: config.setResponseCharset(s);
240:
241: Class cls = parseClass(el,
242: "upload-charset-finder-class",
243: CharsetFinder.class);
244: if (cls != null)
245: config.setUploadCharsetFinder((CharsetFinder) cls
246: .newInstance());
247:
248: cls = parseClass(el, "cache-provider-class",
249: DesktopCacheProvider.class);
250: if (cls != null)
251: config.setDesktopCacheProviderClass(cls);
252:
253: cls = parseClass(el, "ui-factory-class",
254: UiFactory.class);
255: if (cls != null)
256: config.setUiFactoryClass(cls);
257:
258: cls = parseClass(el, "failover-manager-class",
259: FailoverManager.class);
260: if (cls != null)
261: config.setFailoverManagerClass(cls);
262:
263: cls = parseClass(el, "engine-class", UiEngine.class);
264: if (cls != null)
265: config.setUiEngineClass(cls);
266:
267: cls = parseClass(el, "id-generator-class",
268: IdGenerator.class);
269: if (cls != null)
270: config.setIdGeneratorClass(cls);
271:
272: cls = parseClass(el, "web-app-class", WebApp.class);
273: if (cls != null)
274: config.setWebAppClass(cls);
275:
276: cls = parseClass(el, "method-cache-class", Cache.class);
277: if (cls != null)
278: ComponentsCtrl.setEventMethodCache((Cache) cls
279: .newInstance());
280:
281: cls = parseClass(el, "url-encoder-class",
282: Encodes.URLEncoder.class);
283: if (cls != null)
284: Encodes.setURLEncoder((Encodes.URLEncoder) cls
285: .newInstance());
286:
287: s = el.getElementValue("au-writer-class", true);
288: if (s != null)
289: AuWriters
290: .setImplementationClass(s.length() == 0 ? null
291: : Classes.forNameByThread(s));
292: } else if ("xel-config".equals(elnm)) {
293: //xel-config
294: // evaluator-class
295: Class cls = parseClass(el, "evaluator-class",
296: ExpressionFactory.class);
297: if (cls != null)
298: config.setExpressionFactoryClass(cls);
299: } else if ("zscript-config".equals(elnm)) {
300: //zscript-config
301: Interpreters.add(el);
302: //Note: zscript-config is applied to the whole system, not just langdef
303: } else if ("device-config".equals(elnm)) {
304: //device-config
305: Devices.add(el);
306: //Note: device-config is applied to the whole system, not just langdef
307: } else if ("log".equals(elnm)) {
308: final String base = el
309: .getElementValue("log-base", true);
310: if (base != null)
311: org.zkoss.util.logging.LogService.init(base, null); //start the log service
312: } else if ("error-page".equals(elnm)) {
313: //error-page
314: final String clsnm = IDOMs.getRequiredElementValue(el,
315: "exception-type");
316: final String loc = IDOMs.getRequiredElementValue(el,
317: "location");
318: String devType = el
319: .getElementValue("device-type", true);
320: if (devType == null)
321: devType = "ajax";
322: else if (devType.length() == 0)
323: throw new UiException(
324: "device-type not specified at "
325: + el.getLocator());
326:
327: final Class cls;
328: try {
329: cls = Classes.forNameByThread(clsnm);
330: } catch (Throwable ex) {
331: throw new UiException("Unable to load " + clsnm
332: + ", at " + el.getLocator(), ex);
333: }
334:
335: config.addErrorPage(devType, cls, loc);
336: } else if ("preference".equals(elnm)) {
337: final String nm = IDOMs.getRequiredElementValue(el,
338: "name");
339: final String val = IDOMs.getRequiredElementValue(el,
340: "value");
341: config.setPreference(nm, val);
342: } else {
343: throw new UiException("Unknown element: " + elnm
344: + ", at " + el.getLocator());
345: }
346: }
347: }
348:
349: /** Parses desktop-config. */
350: private static void parseDesktopConfig(Configuration config,
351: Element conf) throws Exception {
352: //theme-uri
353: for (Iterator it = conf.getElements("theme-uri").iterator(); it
354: .hasNext();) {
355: final Element el = (Element) it.next();
356: final String uri = el.getText(true);
357: if (uri.length() != 0)
358: config.addThemeURI(uri);
359: }
360:
361: //disable-theme-uri
362: for (Iterator it = conf.getElements("disable-theme-uri")
363: .iterator(); it.hasNext();) {
364: final Element el = (Element) it.next();
365: final String uri = el.getText(true);
366: if (uri.length() != 0)
367: config.addDisabledThemeURI(uri);
368: }
369:
370: //theme-provider-class
371: Class cls = parseClass(conf, "theme-provider-class",
372: ThemeProvider.class);
373: if (cls != null)
374: config.setThemeProvider((ThemeProvider) cls.newInstance());
375:
376: //desktop-timeout
377: Integer v = parseInteger(conf, "desktop-timeout", false);
378: if (v != null)
379: config.setDesktopMaxInactiveInterval(v.intValue());
380:
381: //file-check-period
382: v = parseInteger(conf, "file-check-period", true);
383: if (v != null)
384: System.setProperty("org.zkoss.util.resource.checkPeriod", v
385: .toString());
386: //System-wide property
387:
388: }
389:
390: /** Parses client-config. */
391: private static void parseClientConfig(Configuration config,
392: Element conf) {
393: Integer v = parseInteger(conf, "processing-prompt-delay", true);
394: if (v != null)
395: config.setProcessingPromptDelay(v.intValue());
396:
397: v = parseInteger(conf, "tooltip-delay", true);
398: if (v != null)
399: config.setTooltipDelay(v.intValue());
400:
401: v = parseInteger(conf, "resend-delay", false);
402: if (v != null)
403: config.setResendDelay(v.intValue());
404:
405: String s = conf.getElementValue("keep-across-visits", true);
406: if (s != null)
407: config.setKeepDesktopAcrossVisits(!"false".equals(s));
408:
409: s = conf.getElementValue("disable-behind-modal", true);
410: if (s != null)
411: config.enableDisableBehindModal(!"false".equals(s));
412:
413: //error-reload
414: for (Iterator it = conf.getElements("error-reload").iterator(); it
415: .hasNext();) {
416: final Element el = (Element) it.next();
417:
418: v = parseInteger(el, "error-code", true);
419: if (v == null)
420: throw new UiException("error-code is required, "
421: + el.getLocator());
422: String uri = IDOMs
423: .getRequiredElementValue(el, "reload-uri");
424: if ("false".equals(uri))
425: uri = null;
426:
427: config.addClientErrorReload(v.intValue(), uri);
428: }
429: }
430:
431: /** Parse language-config/addon-uri. */
432: private static void parseLangAddon(Locator locator, Element conf) {
433: for (Iterator it = conf.getElements("addon-uri").iterator(); it
434: .hasNext();) {
435: final Element el = (Element) it.next();
436: final String path = el.getText(true);
437:
438: final URL url = locator.getResource(path);
439: if (url == null)
440: log.error("File not found: " + path + ", at "
441: + el.getLocator());
442: else
443: DefinitionLoaders.addAddon(locator, url);
444: }
445: }
446:
447: /** Parse a class, if specified, whether it implements cls.
448: */
449: private static Class parseClass(Element el, String elnm, Class cls) {
450: //Note: we throw exception rather than warning to make sure
451: //the developer correct it
452: final String clsnm = el.getElementValue(elnm, true);
453: if (clsnm != null && clsnm.length() != 0) {
454: try {
455: final Class klass = Classes.forNameByThread(clsnm);
456: if (cls != null && !cls.isAssignableFrom(klass))
457: throw new UiException(clsnm + " must implement "
458: + cls.getName() + ", " + el.getLocator());
459: // if (log.debuggable()) log.debug("Using "+clsnm+" for "+cls);
460: return klass;
461: } catch (Throwable ex) {
462: throw new UiException("Unable to load " + clsnm
463: + ", at " + el.getLocator());
464: }
465: }
466: return null;
467: }
468:
469: /** Configures an integer. */
470: private static Integer parseInteger(Element el, String subnm,
471: boolean positiveOnly) throws UiException {
472: //Note: we throw exception rather than warning to make sure
473: //the developer correct it
474: String val = el.getElementValue(subnm, true);
475: if (val != null && val.length() > 0) {
476: try {
477: final int v = Integer.parseInt(val);
478: if (positiveOnly && v <= 0)
479: throw new UiException(
480: "The "
481: + subnm
482: + " element must be a positive number, not "
483: + val + ", at " + el.getLocator());
484: return new Integer(v);
485: } catch (NumberFormatException ex) { //eat
486: throw new UiException("The " + subnm
487: + " element must be a number, not " + val
488: + ", at " + el.getLocator());
489: }
490: }
491: return null;
492: }
493: }
|